pixelkiln 0.69.0 → 0.70.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 +582 -422
- package/dist/cli.js.map +1 -1
- package/dist/client/gallery.css +5 -0
- package/dist/client/gallery.js +223 -1
- package/dist/index.cjs +261 -139
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +170 -74
- package/dist/index.d.ts +170 -74
- package/dist/index.js +256 -134
- package/dist/index.js.map +1 -1
- package/docs/REVISIONS.md +11 -0
- package/package.json +1 -1
- package/skills/pixelkiln/references/revisions.md +7 -4
package/dist/index.js
CHANGED
|
@@ -1732,9 +1732,9 @@ var PixelLabClient = class {
|
|
|
1732
1732
|
apiKey;
|
|
1733
1733
|
http;
|
|
1734
1734
|
/** Retries transport failures, 429, and 5xx; see http.ts for the policy. */
|
|
1735
|
-
async request(
|
|
1735
|
+
async request(path35, init) {
|
|
1736
1736
|
const auth = this.apiKey.startsWith("Bearer ") ? this.apiKey : `Bearer ${this.apiKey}`;
|
|
1737
|
-
const res = await this.http(`${BASE}${
|
|
1737
|
+
const res = await this.http(`${BASE}${path35}`, {
|
|
1738
1738
|
...init,
|
|
1739
1739
|
headers: {
|
|
1740
1740
|
Authorization: auth,
|
|
@@ -1744,13 +1744,13 @@ var PixelLabClient = class {
|
|
|
1744
1744
|
});
|
|
1745
1745
|
const text = await res.text();
|
|
1746
1746
|
if (!res.ok) {
|
|
1747
|
-
throw new PixelLabError(`${init?.method ?? "GET"} ${
|
|
1747
|
+
throw new PixelLabError(`${init?.method ?? "GET"} ${path35} \u2192 ${res.status}`, res.status, text);
|
|
1748
1748
|
}
|
|
1749
1749
|
if (!text) return {};
|
|
1750
1750
|
try {
|
|
1751
1751
|
return JSON.parse(text);
|
|
1752
1752
|
} catch {
|
|
1753
|
-
throw new Error(`${init?.method ?? "GET"} ${
|
|
1753
|
+
throw new Error(`${init?.method ?? "GET"} ${path35} returned invalid JSON`);
|
|
1754
1754
|
}
|
|
1755
1755
|
}
|
|
1756
1756
|
async balance() {
|
|
@@ -2142,10 +2142,10 @@ var PixelLabClient = class {
|
|
|
2142
2142
|
const palette = args.paletteSwatchBase64 ? { color_image: { type: "base64", base64: args.paletteSwatchBase64, format: "png" }, force_colors: true } : {};
|
|
2143
2143
|
const encode = (image) => ({ type: "base64", base64: image.base64, format: image.format });
|
|
2144
2144
|
const south = args.reference?.south;
|
|
2145
|
-
let
|
|
2145
|
+
let path35;
|
|
2146
2146
|
let body;
|
|
2147
2147
|
if (args.mode === "v3") {
|
|
2148
|
-
|
|
2148
|
+
path35 = "/create-character-v3";
|
|
2149
2149
|
body = {
|
|
2150
2150
|
description: args.description,
|
|
2151
2151
|
image_size: imageSize,
|
|
@@ -2159,7 +2159,7 @@ var PixelLabClient = class {
|
|
|
2159
2159
|
...args.enhancePrompt ? { enhance_prompt: true } : {}
|
|
2160
2160
|
};
|
|
2161
2161
|
} else if (args.mode === "pro-flash") {
|
|
2162
|
-
|
|
2162
|
+
path35 = "/create-character-pro-flash";
|
|
2163
2163
|
const traits = args.styleTraits;
|
|
2164
2164
|
body = {
|
|
2165
2165
|
description: args.description,
|
|
@@ -2183,7 +2183,7 @@ var PixelLabClient = class {
|
|
|
2183
2183
|
} : {}
|
|
2184
2184
|
};
|
|
2185
2185
|
} else if (args.mode === "pro") {
|
|
2186
|
-
|
|
2186
|
+
path35 = "/create-character-pro";
|
|
2187
2187
|
body = {
|
|
2188
2188
|
description: args.description,
|
|
2189
2189
|
image_size: imageSize,
|
|
@@ -2197,7 +2197,7 @@ var PixelLabClient = class {
|
|
|
2197
2197
|
...!south && args.styleCharacterId ? { style_character_id: args.styleCharacterId } : {}
|
|
2198
2198
|
};
|
|
2199
2199
|
} else {
|
|
2200
|
-
|
|
2200
|
+
path35 = args.directions === 4 ? "/create-character-with-4-directions" : "/create-character-with-8-directions";
|
|
2201
2201
|
body = {
|
|
2202
2202
|
description: args.description,
|
|
2203
2203
|
image_size: imageSize,
|
|
@@ -2214,8 +2214,8 @@ var PixelLabClient = class {
|
|
|
2214
2214
|
...palette
|
|
2215
2215
|
};
|
|
2216
2216
|
}
|
|
2217
|
-
const raw = await this.request(
|
|
2218
|
-
return validateResponse(CharacterSubmitSchema, raw,
|
|
2217
|
+
const raw = await this.request(path35, { method: "POST", body: JSON.stringify(body) });
|
|
2218
|
+
return validateResponse(CharacterSubmitSchema, raw, path35.slice(1));
|
|
2219
2219
|
}
|
|
2220
2220
|
/** A text edit of an existing character, applied to every direction and stored as a sibling. */
|
|
2221
2221
|
async createCharacterState(args) {
|
|
@@ -3393,10 +3393,10 @@ import { createReadStream } from "fs";
|
|
|
3393
3393
|
function sha256(data) {
|
|
3394
3394
|
return createHash("sha256").update(data).digest("hex");
|
|
3395
3395
|
}
|
|
3396
|
-
async function sha256File(
|
|
3396
|
+
async function sha256File(path35) {
|
|
3397
3397
|
return new Promise((resolve, reject) => {
|
|
3398
3398
|
const hash = createHash("sha256");
|
|
3399
|
-
const stream = createReadStream(
|
|
3399
|
+
const stream = createReadStream(path35);
|
|
3400
3400
|
stream.on("data", (chunk2) => hash.update(chunk2));
|
|
3401
3401
|
stream.on("error", reject);
|
|
3402
3402
|
stream.on("end", () => resolve(hash.digest("hex")));
|
|
@@ -5759,9 +5759,9 @@ var RetroDiffusionClient = class {
|
|
|
5759
5759
|
if (!response.ok) throw new ProviderError("retrodiffusion", `Retro Diffusion download failed (${response.status})`, { status: response.status });
|
|
5760
5760
|
return Buffer.from(await response.arrayBuffer());
|
|
5761
5761
|
}
|
|
5762
|
-
async call(
|
|
5762
|
+
async call(path35, init = {}) {
|
|
5763
5763
|
if (!this.token) throw new Error("RD_API_KEY is not set");
|
|
5764
|
-
const response = await this.request(`${this.baseUrl}${
|
|
5764
|
+
const response = await this.request(`${this.baseUrl}${path35}`, {
|
|
5765
5765
|
...init,
|
|
5766
5766
|
headers: {
|
|
5767
5767
|
"Content-Type": "application/json",
|
|
@@ -12050,10 +12050,10 @@ function isSha256Hash(value) {
|
|
|
12050
12050
|
function parseCache(value) {
|
|
12051
12051
|
return HashCacheSchema.parse(value);
|
|
12052
12052
|
}
|
|
12053
|
-
async function loadCache(
|
|
12054
|
-
if (!existsSync13(
|
|
12053
|
+
async function loadCache(path35) {
|
|
12054
|
+
if (!existsSync13(path35)) return { version: 1, hashes: {} };
|
|
12055
12055
|
try {
|
|
12056
|
-
const parsed = HashCacheSchema.safeParse(JSON.parse(await readFile11(
|
|
12056
|
+
const parsed = HashCacheSchema.safeParse(JSON.parse(await readFile11(path35, "utf8")));
|
|
12057
12057
|
if (!parsed.success) return { version: 1, hashes: {} };
|
|
12058
12058
|
return {
|
|
12059
12059
|
version: 1,
|
|
@@ -12065,18 +12065,18 @@ async function loadCache(path34) {
|
|
|
12065
12065
|
return { version: 1, hashes: {} };
|
|
12066
12066
|
}
|
|
12067
12067
|
}
|
|
12068
|
-
async function saveCache(
|
|
12068
|
+
async function saveCache(path35, cache) {
|
|
12069
12069
|
const sorted = {};
|
|
12070
12070
|
for (const key of Object.keys(cache.hashes).sort()) {
|
|
12071
12071
|
const hash = cache.hashes[key];
|
|
12072
12072
|
if (!isSha256Hash(hash)) throw new Error(`Refusing to cache invalid SHA-256 for ${key}`);
|
|
12073
12073
|
sorted[key] = hash;
|
|
12074
12074
|
}
|
|
12075
|
-
await mkdir5(pathModule.dirname(pathModule.resolve(
|
|
12076
|
-
const tmp = `${
|
|
12075
|
+
await mkdir5(pathModule.dirname(pathModule.resolve(path35)), { recursive: true });
|
|
12076
|
+
const tmp = `${path35}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
12077
12077
|
try {
|
|
12078
12078
|
await writeFile5(tmp, JSON.stringify({ version: 1, hashes: sorted }, null, 2) + "\n");
|
|
12079
|
-
await rename4(tmp,
|
|
12079
|
+
await rename4(tmp, path35);
|
|
12080
12080
|
} finally {
|
|
12081
12081
|
await rm4(tmp, { force: true });
|
|
12082
12082
|
}
|
|
@@ -14881,9 +14881,9 @@ async function runPicker(provider, lock, lockPath, opts = {}) {
|
|
|
14881
14881
|
|
|
14882
14882
|
// src/gallery/snapshot.ts
|
|
14883
14883
|
import { createHash as createHash4 } from "crypto";
|
|
14884
|
-
import { existsSync as
|
|
14885
|
-
import { readFile as
|
|
14886
|
-
import
|
|
14884
|
+
import { existsSync as existsSync24 } from "fs";
|
|
14885
|
+
import { readFile as readFile22, stat as stat3 } from "fs/promises";
|
|
14886
|
+
import path30 from "path";
|
|
14887
14887
|
|
|
14888
14888
|
// src/workspace.ts
|
|
14889
14889
|
import { mkdir as mkdir7, readFile as readFile18, rename as rename6, rm as rm7, writeFile as writeFile9 } from "fs/promises";
|
|
@@ -15019,8 +15019,8 @@ function validateWorkspace(ws, dir) {
|
|
|
15019
15019
|
}
|
|
15020
15020
|
|
|
15021
15021
|
// src/gallery/edit.ts
|
|
15022
|
-
import
|
|
15023
|
-
import { z as
|
|
15022
|
+
import path29 from "path";
|
|
15023
|
+
import { z as z11 } from "zod";
|
|
15024
15024
|
|
|
15025
15025
|
// src/manifest-edit.ts
|
|
15026
15026
|
import { readFile as readFile19, rename as rename7, rm as rm8, writeFile as writeFile10 } from "fs/promises";
|
|
@@ -15068,15 +15068,27 @@ var NewAssetSchema = z8.object({
|
|
|
15068
15068
|
/**
|
|
15069
15069
|
* A controlled regeneration of another asset in the same manifest,
|
|
15070
15070
|
* instead of a fresh generation. `inpaint` needs a mask upload the
|
|
15071
|
-
* gallery does not offer yet, so only `image-to-image`
|
|
15072
|
-
*
|
|
15071
|
+
* gallery does not offer yet, so only `image-to-image` and
|
|
15072
|
+
* `animate-skeleton` (whose keypoints file the gallery writes first) can
|
|
15073
|
+
* be created here; hand-edit the manifest for the other modes.
|
|
15073
15074
|
*/
|
|
15074
|
-
revision: z8.
|
|
15075
|
-
|
|
15076
|
-
|
|
15077
|
-
|
|
15078
|
-
|
|
15079
|
-
|
|
15075
|
+
revision: z8.discriminatedUnion("mode", [
|
|
15076
|
+
z8.object({
|
|
15077
|
+
mode: z8.literal("image-to-image"),
|
|
15078
|
+
/** Asset id of the parent this revises; must already exist. */
|
|
15079
|
+
from: z8.string().min(1),
|
|
15080
|
+
strength: z8.number().min(0).max(1).optional()
|
|
15081
|
+
}).strict(),
|
|
15082
|
+
z8.object({
|
|
15083
|
+
mode: z8.literal("animate-skeleton"),
|
|
15084
|
+
from: z8.string().min(1),
|
|
15085
|
+
/** Manifest-relative; checked when the manifest is loaded, like every revision input. */
|
|
15086
|
+
keypointsFile: z8.string().min(1),
|
|
15087
|
+
direction: CharacterDirectionSchema,
|
|
15088
|
+
description: z8.string().min(1).optional(),
|
|
15089
|
+
skeletonTemplate: z8.string().min(1).optional()
|
|
15090
|
+
}).strict()
|
|
15091
|
+
]).optional(),
|
|
15080
15092
|
/** A pose or outfit of an existing `character`/`objectPro` base or state. */
|
|
15081
15093
|
state: NewStateSchema.optional(),
|
|
15082
15094
|
/** A loop of an existing `character`/`objectPro` base or state. */
|
|
@@ -15580,41 +15592,118 @@ function openInEditor(file, editor = process.env.PIXELKILN_EDITOR) {
|
|
|
15580
15592
|
return openExternal(file, command);
|
|
15581
15593
|
}
|
|
15582
15594
|
|
|
15595
|
+
// src/gallery/skeleton.ts
|
|
15596
|
+
import { existsSync as existsSync23 } from "fs";
|
|
15597
|
+
import { mkdir as mkdir9, readFile as readFile21, rm as rm10, writeFile as writeFile12 } from "fs/promises";
|
|
15598
|
+
import path28 from "path";
|
|
15599
|
+
import { z as z10 } from "zod";
|
|
15600
|
+
var SkeletonEstimateRequestSchema = z10.object({
|
|
15601
|
+
project: z10.string().min(1).optional(),
|
|
15602
|
+
styleId: z10.string().min(1),
|
|
15603
|
+
assetId: z10.string().min(1),
|
|
15604
|
+
frames: z10.number().int().min(3).max(15).optional()
|
|
15605
|
+
}).strict();
|
|
15606
|
+
var SkeletonAnimationRequestSchema = z10.object({
|
|
15607
|
+
action: z10.literal("create-skeleton-animation"),
|
|
15608
|
+
project: z10.string().min(1).optional(),
|
|
15609
|
+
expectedSha256: z10.string().regex(/^[0-9a-f]{64}$/),
|
|
15610
|
+
styleId: z10.string().min(1),
|
|
15611
|
+
/** The sprite the animation poses. */
|
|
15612
|
+
from: z10.string().min(1),
|
|
15613
|
+
assetId: z10.string().min(1).regex(/^[^/\\]+$/, "asset ids cannot contain slashes"),
|
|
15614
|
+
/** The motion, sent as PixelLab's `action`. */
|
|
15615
|
+
prompt: z10.string().min(1),
|
|
15616
|
+
direction: CharacterDirectionSchema,
|
|
15617
|
+
description: z10.string().min(1).optional(),
|
|
15618
|
+
skeletonTemplate: z10.string().min(1).optional(),
|
|
15619
|
+
/** Manifest-relative JSON path inside the project. */
|
|
15620
|
+
keypointsFile: z10.string().min(1),
|
|
15621
|
+
/** Poses to write to `keypointsFile`; omitted to use a file that already exists. */
|
|
15622
|
+
set: SkeletonSetSchema.optional(),
|
|
15623
|
+
/** Replace an existing `keypointsFile` with `set`. */
|
|
15624
|
+
overwrite: z10.boolean().optional()
|
|
15625
|
+
}).strict();
|
|
15626
|
+
async function createSkeletonAnimation(manifestPath, request) {
|
|
15627
|
+
const root = path28.dirname(path28.resolve(manifestPath));
|
|
15628
|
+
const file = path28.resolve(root, request.keypointsFile);
|
|
15629
|
+
const relative = path28.relative(root, file);
|
|
15630
|
+
if (!request.keypointsFile.endsWith(".json") || relative.startsWith("..") || path28.isAbsolute(relative)) {
|
|
15631
|
+
throw new ManifestEditError("the keypoints file must be a .json path inside the project");
|
|
15632
|
+
}
|
|
15633
|
+
const existed = existsSync23(file);
|
|
15634
|
+
let previous = null;
|
|
15635
|
+
if (request.set) {
|
|
15636
|
+
if (existed && !request.overwrite) {
|
|
15637
|
+
throw new ManifestEditError(`${request.keypointsFile} already exists; choose another path, or replace it`);
|
|
15638
|
+
}
|
|
15639
|
+
if (existed) previous = await readFile21(file);
|
|
15640
|
+
await mkdir9(path28.dirname(file), { recursive: true });
|
|
15641
|
+
await writeFile12(file, JSON.stringify(request.set, null, 2) + "\n");
|
|
15642
|
+
} else if (!existed) {
|
|
15643
|
+
throw new ManifestEditError(`${request.keypointsFile} does not exist; paste poses or estimate them from the sprite`);
|
|
15644
|
+
}
|
|
15645
|
+
try {
|
|
15646
|
+
await applyManifestEdit(manifestPath, {
|
|
15647
|
+
action: "add-asset",
|
|
15648
|
+
assetId: request.assetId,
|
|
15649
|
+
expectedSha256: request.expectedSha256,
|
|
15650
|
+
asset: {
|
|
15651
|
+
prompt: request.prompt,
|
|
15652
|
+
styles: [request.styleId],
|
|
15653
|
+
revision: {
|
|
15654
|
+
mode: "animate-skeleton",
|
|
15655
|
+
from: request.from,
|
|
15656
|
+
keypointsFile: relative.split(path28.sep).join("/"),
|
|
15657
|
+
direction: request.direction,
|
|
15658
|
+
...request.description ? { description: request.description } : {},
|
|
15659
|
+
...request.skeletonTemplate ? { skeletonTemplate: request.skeletonTemplate } : {}
|
|
15660
|
+
}
|
|
15661
|
+
}
|
|
15662
|
+
});
|
|
15663
|
+
} catch (error) {
|
|
15664
|
+
if (request.set) {
|
|
15665
|
+
if (previous) await writeFile12(file, previous);
|
|
15666
|
+
else await rm10(file, { force: true });
|
|
15667
|
+
}
|
|
15668
|
+
throw error;
|
|
15669
|
+
}
|
|
15670
|
+
}
|
|
15671
|
+
|
|
15583
15672
|
// src/gallery/edit.ts
|
|
15584
|
-
var Base64 =
|
|
15585
|
-
var HandEditRequestSchema =
|
|
15586
|
-
|
|
15587
|
-
action:
|
|
15588
|
-
project:
|
|
15589
|
-
assetId:
|
|
15590
|
-
styleId:
|
|
15591
|
-
expectedSha256:
|
|
15673
|
+
var Base64 = z11.string().regex(/^[A-Za-z0-9+/]*={0,2}$/).max(Math.ceil(MAX_HAND_EDIT_BYTES / 3) * 4);
|
|
15674
|
+
var HandEditRequestSchema = z11.discriminatedUnion("action", [
|
|
15675
|
+
z11.object({
|
|
15676
|
+
action: z11.literal("start-edit"),
|
|
15677
|
+
project: z11.string().min(1).optional(),
|
|
15678
|
+
assetId: z11.string().min(1),
|
|
15679
|
+
styleId: z11.string().min(1),
|
|
15680
|
+
expectedSha256: z11.string().regex(/^[0-9a-f]{64}$/),
|
|
15592
15681
|
/** Also launch the author's editor on the file. */
|
|
15593
|
-
open:
|
|
15682
|
+
open: z11.boolean().optional()
|
|
15594
15683
|
}).strict(),
|
|
15595
|
-
|
|
15596
|
-
action:
|
|
15597
|
-
project:
|
|
15598
|
-
assetId:
|
|
15599
|
-
styleId:
|
|
15600
|
-
expectedSha256:
|
|
15684
|
+
z11.object({
|
|
15685
|
+
action: z11.literal("detach-edit"),
|
|
15686
|
+
project: z11.string().min(1).optional(),
|
|
15687
|
+
assetId: z11.string().min(1),
|
|
15688
|
+
styleId: z11.string().min(1),
|
|
15689
|
+
expectedSha256: z11.string().regex(/^[0-9a-f]{64}$/)
|
|
15601
15690
|
}).strict(),
|
|
15602
15691
|
/** From the in-browser editor: the page holds the bytes, the server validates and writes. */
|
|
15603
|
-
|
|
15604
|
-
action:
|
|
15605
|
-
project:
|
|
15606
|
-
assetId:
|
|
15607
|
-
styleId:
|
|
15608
|
-
expectedSha256:
|
|
15692
|
+
z11.object({
|
|
15693
|
+
action: z11.literal("save-edit"),
|
|
15694
|
+
project: z11.string().min(1).optional(),
|
|
15695
|
+
assetId: z11.string().min(1),
|
|
15696
|
+
styleId: z11.string().min(1),
|
|
15697
|
+
expectedSha256: z11.string().regex(/^[0-9a-f]{64}$/),
|
|
15609
15698
|
/** The flattened image; for a frame set, `frames` instead. */
|
|
15610
15699
|
png: Base64.min(1).optional(),
|
|
15611
15700
|
/** Every frame of a set, tagged with the role it was opened under (null for one added in the editor). */
|
|
15612
|
-
frames:
|
|
15701
|
+
frames: z11.array(z11.object({ role: z11.string().min(1).nullable(), png: Base64.min(1) }).strict()).min(1).max(256).optional(),
|
|
15613
15702
|
/** The editor's layered project file, kept beside the edit. */
|
|
15614
15703
|
pxo: Base64.optional(),
|
|
15615
15704
|
/** Editor identity from the bridge's ready message. */
|
|
15616
|
-
editor:
|
|
15617
|
-
protocol:
|
|
15705
|
+
editor: z11.string().min(1).max(200),
|
|
15706
|
+
protocol: z11.number().int().positive()
|
|
15618
15707
|
}).strict()
|
|
15619
15708
|
]);
|
|
15620
15709
|
function createGalleryEditHandler(opts) {
|
|
@@ -15655,6 +15744,23 @@ function createGalleryEditHandler(opts) {
|
|
|
15655
15744
|
}
|
|
15656
15745
|
return opts.reload();
|
|
15657
15746
|
}
|
|
15747
|
+
const skeleton = SkeletonAnimationRequestSchema.safeParse(body);
|
|
15748
|
+
if (skeleton.success) {
|
|
15749
|
+
let manifestPath2;
|
|
15750
|
+
try {
|
|
15751
|
+
manifestPath2 = await opts.manifestFor(skeleton.data.project);
|
|
15752
|
+
} catch (error) {
|
|
15753
|
+
throw new ManifestEditError(error instanceof Error ? error.message : String(error));
|
|
15754
|
+
}
|
|
15755
|
+
await createSkeletonAnimation(manifestPath2, skeleton.data);
|
|
15756
|
+
log(` manifest edited: added skeleton animation ${skeleton.data.assetId} (${skeleton.data.keypointsFile})`);
|
|
15757
|
+
return opts.reload();
|
|
15758
|
+
}
|
|
15759
|
+
if (body?.action === "create-skeleton-animation") {
|
|
15760
|
+
throw new ManifestEditError(
|
|
15761
|
+
"invalid skeleton animation: " + skeleton.error.issues.slice(0, 3).map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; ")
|
|
15762
|
+
);
|
|
15763
|
+
}
|
|
15658
15764
|
const parsed = ManifestEditSchema.safeParse(body);
|
|
15659
15765
|
if (!parsed.success) {
|
|
15660
15766
|
throw new ManifestEditError(
|
|
@@ -15669,7 +15775,7 @@ function createGalleryEditHandler(opts) {
|
|
|
15669
15775
|
}
|
|
15670
15776
|
const result = await applyManifestEdit(manifestPath, parsed.data);
|
|
15671
15777
|
if (result.changed) {
|
|
15672
|
-
const relative =
|
|
15778
|
+
const relative = path29.relative(process.cwd(), result.manifestPath);
|
|
15673
15779
|
const shown = !relative || relative.startsWith("..") ? result.manifestPath : relative;
|
|
15674
15780
|
const subject = parsed.data.action === "patch-style" ? `style ${parsed.data.styleId}` : parsed.data.action === "set-source" || parsed.data.action === "clear-source" ? `${parsed.data.styleId}/${parsed.data.assetId} source` : parsed.data.assetId;
|
|
15675
15781
|
log(` manifest edited: ${parsed.data.action === "add-asset" ? "added" : "changed"} ${subject} in ${shown}`);
|
|
@@ -15681,7 +15787,7 @@ function createGalleryEditHandler(opts) {
|
|
|
15681
15787
|
// src/gallery/snapshot.ts
|
|
15682
15788
|
var PROJECT_FILE_TYPE = "application/octet-stream";
|
|
15683
15789
|
function galleryMediaId(absolutePath) {
|
|
15684
|
-
return createHash4("sha256").update(
|
|
15790
|
+
return createHash4("sha256").update(path30.resolve(absolutePath)).digest("hex").slice(0, 24);
|
|
15685
15791
|
}
|
|
15686
15792
|
function galleryMediaRoute(id) {
|
|
15687
15793
|
return `/media/${id}`;
|
|
@@ -15700,9 +15806,9 @@ function describeHistory(media, entry, cacheDir) {
|
|
|
15700
15806
|
return (entry.history ?? []).map((generation, i) => {
|
|
15701
15807
|
const outputs = generation.outputs.map((output) => {
|
|
15702
15808
|
const mediaType = output.mediaType ?? MediaType.PNG;
|
|
15703
|
-
const file =
|
|
15809
|
+
const file = path30.join(cacheDir, cacheFileName(output.sha256, mediaType));
|
|
15704
15810
|
let url = null;
|
|
15705
|
-
if (
|
|
15811
|
+
if (existsSync24(file)) {
|
|
15706
15812
|
const id = galleryMediaId(file);
|
|
15707
15813
|
media.set(id, { path: file, contentType: mediaType });
|
|
15708
15814
|
url = `${galleryMediaRoute(id)}?v=${output.sha256.slice(0, 24)}`;
|
|
@@ -15734,7 +15840,7 @@ function describeHistory(media, entry, cacheDir) {
|
|
|
15734
15840
|
var MAX_COMPARED_BYTES = 8 * 1024 * 1024;
|
|
15735
15841
|
async function samePixels(a, b) {
|
|
15736
15842
|
try {
|
|
15737
|
-
const [left, right] = await Promise.all([
|
|
15843
|
+
const [left, right] = await Promise.all([readFile22(a), readFile22(b)]);
|
|
15738
15844
|
if (left.length > MAX_COMPARED_BYTES || right.length > MAX_COMPARED_BYTES) return false;
|
|
15739
15845
|
const x = decodePng(left);
|
|
15740
15846
|
const y = decodePng(right);
|
|
@@ -15832,10 +15938,10 @@ async function describeOutput(media, root, absolutePath, recorded = {}) {
|
|
|
15832
15938
|
async function describeQuality(media, root, inspection) {
|
|
15833
15939
|
const outputPaths = inspection.outputs ?? [inspection.output];
|
|
15834
15940
|
const outputs = await Promise.all(
|
|
15835
|
-
outputPaths.map((file) => describeOutput(media, root,
|
|
15941
|
+
outputPaths.map((file) => describeOutput(media, root, path30.resolve(root, file)))
|
|
15836
15942
|
);
|
|
15837
|
-
const recordPath =
|
|
15838
|
-
const recordExists =
|
|
15943
|
+
const recordPath = path30.resolve(root, inspection.record);
|
|
15944
|
+
const recordExists = existsSync24(recordPath);
|
|
15839
15945
|
let options = null;
|
|
15840
15946
|
let check = null;
|
|
15841
15947
|
if (recordExists) {
|
|
@@ -15854,7 +15960,7 @@ async function describeQuality(media, root, inspection) {
|
|
|
15854
15960
|
return {
|
|
15855
15961
|
state: inspection.state,
|
|
15856
15962
|
reason: inspection.reason,
|
|
15857
|
-
output: portableOutputPath(
|
|
15963
|
+
output: portableOutputPath(path30.resolve(root, inspection.output), root),
|
|
15858
15964
|
record: portableOutputPath(recordPath, root),
|
|
15859
15965
|
recordExists,
|
|
15860
15966
|
outputs,
|
|
@@ -15892,9 +15998,9 @@ async function buildGallerySnapshot(opts) {
|
|
|
15892
15998
|
const media = /* @__PURE__ */ new Map();
|
|
15893
15999
|
const plan = await buildPlan(specs, lock);
|
|
15894
16000
|
const items = [];
|
|
15895
|
-
const manifestText = await
|
|
16001
|
+
const manifestText = await readFile22(loaded.path, "utf8");
|
|
15896
16002
|
const manifestSha256 = sha256(manifestText);
|
|
15897
|
-
const cacheDir =
|
|
16003
|
+
const cacheDir = path30.resolve(path30.dirname(opts.lockPath), ".pixelkiln", "cache");
|
|
15898
16004
|
let rawStyles = {};
|
|
15899
16005
|
try {
|
|
15900
16006
|
const raw = JSON.parse(manifestText);
|
|
@@ -15918,12 +16024,12 @@ async function buildGallerySnapshot(opts) {
|
|
|
15918
16024
|
)
|
|
15919
16025
|
);
|
|
15920
16026
|
if (spec.source) {
|
|
15921
|
-
const editPath =
|
|
16027
|
+
const editPath = path30.resolve(root, spec.source);
|
|
15922
16028
|
const memberCount = sourceIsStem(spec.source, entry, root) ? entry.outputs.length : 1;
|
|
15923
16029
|
const editShas = await Promise.all(
|
|
15924
16030
|
Array.from({ length: memberCount }, async (_, index) => {
|
|
15925
16031
|
const file = sourceOutputPath(spec.source, entry, index, root);
|
|
15926
|
-
return { file, sha256:
|
|
16032
|
+
return { file, sha256: existsSync24(file) ? await sha256File(file) : null };
|
|
15927
16033
|
})
|
|
15928
16034
|
);
|
|
15929
16035
|
edits = await Promise.all(editShas.map(({ file, sha256: editSha }) => describeOutput(media, root, file, { sha256: editSha })));
|
|
@@ -15931,7 +16037,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
15931
16037
|
const companion = await readHandEditCompanion(editPath);
|
|
15932
16038
|
if (companion) {
|
|
15933
16039
|
const projectPath = handEditProjectPath(editPath);
|
|
15934
|
-
const project = companion.project &&
|
|
16040
|
+
const project = companion.project && existsSync24(projectPath) ? await fileInfo(projectPath) : null;
|
|
15935
16041
|
let projectUrl = null;
|
|
15936
16042
|
if (project) {
|
|
15937
16043
|
const id = galleryMediaId(projectPath);
|
|
@@ -15967,8 +16073,8 @@ async function buildGallerySnapshot(opts) {
|
|
|
15967
16073
|
editStatus = edits.some((member) => !member.exists) ? "missing" : editChanged.every((changed) => !changed) ? "same" : regenerated ? "regenerated-since" : "edited";
|
|
15968
16074
|
}
|
|
15969
16075
|
} else if (spec.source) {
|
|
15970
|
-
outputs = [await describeOutput(media, root,
|
|
15971
|
-
} else if (!entry &&
|
|
16076
|
+
outputs = [await describeOutput(media, root, path30.resolve(root, spec.source))];
|
|
16077
|
+
} else if (!entry && existsSync24(spec.outFile)) {
|
|
15972
16078
|
outputs = [await describeOutput(media, root, spec.outFile)];
|
|
15973
16079
|
} else {
|
|
15974
16080
|
outputs = [];
|
|
@@ -16009,6 +16115,10 @@ async function buildGallerySnapshot(opts) {
|
|
|
16009
16115
|
revision: entry?.revision ?? null,
|
|
16010
16116
|
revisionParentKey: spec.revision ? lockKey(spec.styleId, spec.revision.sourceAssetId) : entry?.revision ? lockKey(spec.styleId, entry.revision.sourceAssetId) : null,
|
|
16011
16117
|
mirrorOfKey: spec.mirror ? lockKey(spec.styleId, spec.mirror.sourceAssetId) : entry?.mirror ? lockKey(spec.styleId, entry.mirror.sourceAssetId) : null,
|
|
16118
|
+
skeleton: spec.revision?.mode === "animate-skeleton" && spec.revision.keypointsFile ? {
|
|
16119
|
+
keypointsFile: portableOutputPath(spec.revision.keypointsFile, root),
|
|
16120
|
+
set: spec.revision.skeleton ?? null
|
|
16121
|
+
} : null,
|
|
16012
16122
|
character: describeCharacter(spec, entry),
|
|
16013
16123
|
outputs,
|
|
16014
16124
|
quality,
|
|
@@ -16073,6 +16183,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
16073
16183
|
revision: entry.revision,
|
|
16074
16184
|
revisionParentKey: entry.revision ? lockKey(entry.styleId, entry.revision.sourceAssetId) : null,
|
|
16075
16185
|
mirrorOfKey: entry.mirror ? lockKey(entry.styleId, entry.mirror.sourceAssetId) : null,
|
|
16186
|
+
skeleton: null,
|
|
16076
16187
|
character: describeCharacter(void 0, entry),
|
|
16077
16188
|
outputs,
|
|
16078
16189
|
quality: null,
|
|
@@ -16163,7 +16274,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
16163
16274
|
id: loaded.manifest.name,
|
|
16164
16275
|
name: loaded.manifest.name,
|
|
16165
16276
|
manifest: loaded.path,
|
|
16166
|
-
lock:
|
|
16277
|
+
lock: path30.resolve(opts.lockPath),
|
|
16167
16278
|
root,
|
|
16168
16279
|
provider: loaded.manifest.provider,
|
|
16169
16280
|
account: null,
|
|
@@ -16189,7 +16300,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
16189
16300
|
return { snapshot, media };
|
|
16190
16301
|
}
|
|
16191
16302
|
async function buildWorkspaceGallerySnapshot(opts) {
|
|
16192
|
-
const dir =
|
|
16303
|
+
const dir = path30.dirname(path30.resolve(opts.workspacePath));
|
|
16193
16304
|
const media = /* @__PURE__ */ new Map();
|
|
16194
16305
|
const projects = [];
|
|
16195
16306
|
const items = [];
|
|
@@ -16232,11 +16343,11 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
16232
16343
|
id: project.id,
|
|
16233
16344
|
name: loaded.manifest.name,
|
|
16234
16345
|
manifest: manifestPath,
|
|
16235
|
-
lock:
|
|
16346
|
+
lock: path30.resolve(lockPath),
|
|
16236
16347
|
root: loaded.root,
|
|
16237
16348
|
provider: loaded.manifest.provider,
|
|
16238
16349
|
account: project.account ?? null,
|
|
16239
|
-
manifestSha256: manifestSha256 ?? sha256(await
|
|
16350
|
+
manifestSha256: manifestSha256 ?? sha256(await readFile22(manifestPath, "utf8")),
|
|
16240
16351
|
historyLimit: historyLimit(loaded.manifest),
|
|
16241
16352
|
entries: Object.keys(lock.entries).length,
|
|
16242
16353
|
items: projectItems.length,
|
|
@@ -16248,8 +16359,8 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
16248
16359
|
id: project.id,
|
|
16249
16360
|
name: project.id,
|
|
16250
16361
|
manifest: manifestPath,
|
|
16251
|
-
lock:
|
|
16252
|
-
root:
|
|
16362
|
+
lock: path30.resolve(lockPath),
|
|
16363
|
+
root: path30.dirname(manifestPath),
|
|
16253
16364
|
provider: project.provider,
|
|
16254
16365
|
account: project.account ?? null,
|
|
16255
16366
|
manifestSha256: null,
|
|
@@ -16276,7 +16387,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
16276
16387
|
version: 1,
|
|
16277
16388
|
generatedAt: (opts.now ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
|
|
16278
16389
|
project: null,
|
|
16279
|
-
workspace: { path:
|
|
16390
|
+
workspace: { path: path30.resolve(opts.workspacePath), dir, projects },
|
|
16280
16391
|
filter,
|
|
16281
16392
|
totals: {
|
|
16282
16393
|
entries: projects.reduce((sum, project) => sum + project.entries, 0),
|
|
@@ -16425,10 +16536,10 @@ var EDITOR_RELEASE_BASE = "https://github.com/gfargo/pixelkiln/releases/download
|
|
|
16425
16536
|
|
|
16426
16537
|
// src/editor/install.ts
|
|
16427
16538
|
import { createHash as createHash5 } from "crypto";
|
|
16428
|
-
import { existsSync as
|
|
16429
|
-
import { mkdir as
|
|
16539
|
+
import { existsSync as existsSync25 } from "fs";
|
|
16540
|
+
import { mkdir as mkdir10, readFile as readFile23, rename as rename9, rm as rm11, stat as stat4, writeFile as writeFile13 } from "fs/promises";
|
|
16430
16541
|
import os2 from "os";
|
|
16431
|
-
import
|
|
16542
|
+
import path31 from "path";
|
|
16432
16543
|
var EditorInstallError = class extends Error {
|
|
16433
16544
|
constructor(message6, file) {
|
|
16434
16545
|
super(message6);
|
|
@@ -16439,15 +16550,15 @@ var EditorInstallError = class extends Error {
|
|
|
16439
16550
|
};
|
|
16440
16551
|
function editorToolsRoot() {
|
|
16441
16552
|
const override = process.env.PIXELKILN_TOOLS_DIR?.trim();
|
|
16442
|
-
if (override) return
|
|
16443
|
-
if (process.platform === "darwin") return
|
|
16553
|
+
if (override) return path31.resolve(override);
|
|
16554
|
+
if (process.platform === "darwin") return path31.join(os2.homedir(), "Library", "Caches", "pixelkiln", "tools");
|
|
16444
16555
|
if (process.platform === "win32") {
|
|
16445
|
-
return
|
|
16556
|
+
return path31.join(process.env.LOCALAPPDATA ?? path31.join(os2.homedir(), "AppData", "Local"), "pixelkiln", "tools");
|
|
16446
16557
|
}
|
|
16447
|
-
return
|
|
16558
|
+
return path31.join(process.env.XDG_CACHE_HOME ?? path31.join(os2.homedir(), ".cache"), "pixelkiln", "tools");
|
|
16448
16559
|
}
|
|
16449
16560
|
function editorDir(pin = EDITOR_PIN, root = editorToolsRoot()) {
|
|
16450
|
-
return
|
|
16561
|
+
return path31.join(root, "pixelorama", pin.release ?? "unpublished");
|
|
16451
16562
|
}
|
|
16452
16563
|
function editorBaseUrl(pin = EDITOR_PIN) {
|
|
16453
16564
|
const override = process.env.PIXELKILN_EDITOR_URL?.trim();
|
|
@@ -16457,7 +16568,7 @@ function editorBaseUrl(pin = EDITOR_PIN) {
|
|
|
16457
16568
|
}
|
|
16458
16569
|
async function sha256Of(file) {
|
|
16459
16570
|
try {
|
|
16460
|
-
return createHash5("sha256").update(await
|
|
16571
|
+
return createHash5("sha256").update(await readFile23(file)).digest("hex");
|
|
16461
16572
|
} catch {
|
|
16462
16573
|
return null;
|
|
16463
16574
|
}
|
|
@@ -16470,8 +16581,8 @@ async function editorStatus(opts = {}) {
|
|
|
16470
16581
|
let totalBytes = 0;
|
|
16471
16582
|
for (const [name, expected] of Object.entries(pin.files)) {
|
|
16472
16583
|
totalBytes += expected.bytes;
|
|
16473
|
-
const file =
|
|
16474
|
-
const info =
|
|
16584
|
+
const file = path31.join(dir, name);
|
|
16585
|
+
const info = existsSync25(file) ? await stat4(file).catch(() => null) : null;
|
|
16475
16586
|
if (info && info.size === expected.bytes && await sha256Of(file) === expected.sha256) {
|
|
16476
16587
|
installedBytes += expected.bytes;
|
|
16477
16588
|
} else {
|
|
@@ -16500,7 +16611,7 @@ async function installEditor(opts = {}) {
|
|
|
16500
16611
|
);
|
|
16501
16612
|
}
|
|
16502
16613
|
if (!baseUrl) throw new EditorInstallError("no download location for the editor build");
|
|
16503
|
-
await
|
|
16614
|
+
await mkdir10(dir, { recursive: true });
|
|
16504
16615
|
const downloaded = [];
|
|
16505
16616
|
const skipped = Object.keys(pin.files).filter((name) => !before.missing.includes(name));
|
|
16506
16617
|
let fetchedBytes = before.installedBytes;
|
|
@@ -16528,13 +16639,13 @@ async function installEditor(opts = {}) {
|
|
|
16528
16639
|
if (digest2 !== expected.sha256) {
|
|
16529
16640
|
throw new EditorInstallError(`${name}: sha256 ${digest2.slice(0, 12)}\u2026 does not match the pinned ${expected.sha256.slice(0, 12)}\u2026`, name);
|
|
16530
16641
|
}
|
|
16531
|
-
const final =
|
|
16642
|
+
const final = path31.join(dir, name);
|
|
16532
16643
|
const tmp = `${final}.${process.pid}.part`;
|
|
16533
16644
|
try {
|
|
16534
|
-
await
|
|
16645
|
+
await writeFile13(tmp, bytes);
|
|
16535
16646
|
await rename9(tmp, final);
|
|
16536
16647
|
} finally {
|
|
16537
|
-
await
|
|
16648
|
+
await rm11(tmp, { force: true });
|
|
16538
16649
|
}
|
|
16539
16650
|
fetchedBytes += bytes.length;
|
|
16540
16651
|
downloaded.push(name);
|
|
@@ -16574,12 +16685,12 @@ var EDITOR_CONTENT_TYPES = {
|
|
|
16574
16685
|
".json": "application/json; charset=utf-8"
|
|
16575
16686
|
};
|
|
16576
16687
|
function editorContentType(name) {
|
|
16577
|
-
return EDITOR_CONTENT_TYPES[
|
|
16688
|
+
return EDITOR_CONTENT_TYPES[path31.extname(name).toLowerCase()] ?? null;
|
|
16578
16689
|
}
|
|
16579
16690
|
|
|
16580
16691
|
// src/gallery/editor.ts
|
|
16581
|
-
import { readFile as
|
|
16582
|
-
import
|
|
16692
|
+
import { readFile as readFile24 } from "fs/promises";
|
|
16693
|
+
import path32 from "path";
|
|
16583
16694
|
function editorRoute(release, name = "index.html") {
|
|
16584
16695
|
return `/editor/${encodeURIComponent(release)}/${encodeURIComponent(name)}`;
|
|
16585
16696
|
}
|
|
@@ -16652,7 +16763,7 @@ function createGalleryEditorHandlers(opts = {}) {
|
|
|
16652
16763
|
if (!status.installed) return null;
|
|
16653
16764
|
let bytes;
|
|
16654
16765
|
try {
|
|
16655
|
-
bytes = await
|
|
16766
|
+
bytes = await readFile24(path32.join(dir, name));
|
|
16656
16767
|
} catch {
|
|
16657
16768
|
verified = null;
|
|
16658
16769
|
return null;
|
|
@@ -16669,7 +16780,7 @@ function createGalleryEditorHandlers(opts = {}) {
|
|
|
16669
16780
|
// src/gallery/server.ts
|
|
16670
16781
|
import { randomBytes } from "crypto";
|
|
16671
16782
|
import { createServer as createServer2 } from "http";
|
|
16672
|
-
import { readFile as
|
|
16783
|
+
import { readFile as readFile25 } from "fs/promises";
|
|
16673
16784
|
var MAX_EDIT_BYTES = 48 * 1024 * 1024;
|
|
16674
16785
|
var EDITOR_CSP = [
|
|
16675
16786
|
"default-src 'self'",
|
|
@@ -16697,7 +16808,7 @@ async function readJsonBody(req) {
|
|
|
16697
16808
|
async function serveGallery(opts) {
|
|
16698
16809
|
const log = opts.onProgress ?? (() => {
|
|
16699
16810
|
});
|
|
16700
|
-
const session = opts.edit || opts.generate || opts.editor ? randomBytes(16).toString("hex") : null;
|
|
16811
|
+
const session = opts.edit || opts.generate || opts.editor || opts.skeleton ? randomBytes(16).toString("hex") : null;
|
|
16701
16812
|
let media = /* @__PURE__ */ new Map();
|
|
16702
16813
|
let loading = null;
|
|
16703
16814
|
const reviewAssets = /* @__PURE__ */ new Map();
|
|
@@ -16772,6 +16883,17 @@ async function serveGallery(opts) {
|
|
|
16772
16883
|
}
|
|
16773
16884
|
return;
|
|
16774
16885
|
}
|
|
16886
|
+
if (req.method === "POST" && url2.pathname === "/api/skeleton/estimate") {
|
|
16887
|
+
if (!opts.skeleton || !session) return fail(405, "skeleton estimates need the gallery started with --edit");
|
|
16888
|
+
try {
|
|
16889
|
+
const body = await guardedBody("skeleton estimates");
|
|
16890
|
+
if (body === void 0) return;
|
|
16891
|
+
json(200, await opts.skeleton.estimate(body));
|
|
16892
|
+
} catch (err) {
|
|
16893
|
+
reportError("skeleton estimate", err);
|
|
16894
|
+
}
|
|
16895
|
+
return;
|
|
16896
|
+
}
|
|
16775
16897
|
if (req.method === "POST" && url2.pathname === "/api/editor/install") {
|
|
16776
16898
|
if (!opts.editor || !session) return fail(405, "the in-browser editor is off for this gallery");
|
|
16777
16899
|
try {
|
|
@@ -16828,7 +16950,7 @@ async function serveGallery(opts) {
|
|
|
16828
16950
|
const asset = reviewAssets.get(jobId)?.get(url2.pathname);
|
|
16829
16951
|
if (!asset) return fail(404, "no such review asset");
|
|
16830
16952
|
try {
|
|
16831
|
-
const bytes = await
|
|
16953
|
+
const bytes = await readFile25(asset.path);
|
|
16832
16954
|
res.writeHead(200, {
|
|
16833
16955
|
"Content-Type": asset.contentType,
|
|
16834
16956
|
"Cache-Control": "no-store",
|
|
@@ -16904,7 +17026,7 @@ async function serveGallery(opts) {
|
|
|
16904
17026
|
if (!asset) return fail(404, "no such gallery media");
|
|
16905
17027
|
let bytes;
|
|
16906
17028
|
try {
|
|
16907
|
-
bytes = await
|
|
17029
|
+
bytes = await readFile25(asset.path);
|
|
16908
17030
|
} catch {
|
|
16909
17031
|
return fail(404, "gallery media is no longer on disk; refresh the page");
|
|
16910
17032
|
}
|
|
@@ -16948,27 +17070,27 @@ async function serveGallery(opts) {
|
|
|
16948
17070
|
|
|
16949
17071
|
// src/gallery/generate.ts
|
|
16950
17072
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
16951
|
-
import { z as
|
|
16952
|
-
var GenerateRequestSchema =
|
|
16953
|
-
project:
|
|
16954
|
-
keys:
|
|
17073
|
+
import { z as z12 } from "zod";
|
|
17074
|
+
var GenerateRequestSchema = z12.object({
|
|
17075
|
+
project: z12.string().min(1).optional(),
|
|
17076
|
+
keys: z12.array(z12.string().min(1)).min(1).max(500),
|
|
16955
17077
|
/**
|
|
16956
17078
|
* Regenerate work that is up to date, as `gen --force` does, and let the
|
|
16957
17079
|
* fetch replace a locally modified or untracked file. With `restore`, put
|
|
16958
17080
|
* the recorded bytes back over a modified file.
|
|
16959
17081
|
*/
|
|
16960
|
-
force:
|
|
17082
|
+
force: z12.boolean().optional(),
|
|
16961
17083
|
/** Advance in-flight, review, or selected work without submitting anything. */
|
|
16962
|
-
resume:
|
|
17084
|
+
resume: z12.boolean().optional(),
|
|
16963
17085
|
/** Re-download downloaded outputs and replace files whose object changed upstream. */
|
|
16964
|
-
refresh:
|
|
17086
|
+
refresh: z12.boolean().optional(),
|
|
16965
17087
|
/** Put recorded outputs back on disk from the cache or the provider, without generating. */
|
|
16966
|
-
restore:
|
|
17088
|
+
restore: z12.boolean().optional(),
|
|
16967
17089
|
/**
|
|
16968
17090
|
* Bring a replaced generation back: 1-based position in the record's
|
|
16969
17091
|
* history (newest first) or an output hash prefix. One key at a time.
|
|
16970
17092
|
*/
|
|
16971
|
-
revert:
|
|
17093
|
+
revert: z12.string().min(1).max(64).optional()
|
|
16972
17094
|
}).strict().refine((request) => [request.resume, request.refresh, request.restore, request.revert !== void 0].filter(Boolean).length <= 1, {
|
|
16973
17095
|
message: "resume, refresh, restore, and revert are exclusive"
|
|
16974
17096
|
}).refine((request) => request.revert === void 0 || request.keys.length === 1, { message: "revert takes one key" });
|
|
@@ -17278,19 +17400,19 @@ function createGenerateHandlers(opts) {
|
|
|
17278
17400
|
}
|
|
17279
17401
|
|
|
17280
17402
|
// src/pipeline/salvage.ts
|
|
17281
|
-
import { readFile as
|
|
17282
|
-
import { existsSync as
|
|
17283
|
-
import
|
|
17403
|
+
import { readFile as readFile26 } from "fs/promises";
|
|
17404
|
+
import { existsSync as existsSync26 } from "fs";
|
|
17405
|
+
import path33 from "path";
|
|
17284
17406
|
function providerClaimId(provider, objectId) {
|
|
17285
17407
|
return `${encodeURIComponent(provider)}:${encodeURIComponent(objectId)}`;
|
|
17286
17408
|
}
|
|
17287
17409
|
async function loadClaims(lockPaths, opts = {}) {
|
|
17288
17410
|
const claimed = /* @__PURE__ */ new Set();
|
|
17289
17411
|
for (const p of lockPaths) {
|
|
17290
|
-
if (!
|
|
17412
|
+
if (!existsSync26(p)) throw new Error(`Claim lockfile not found: ${p}`);
|
|
17291
17413
|
let parsed;
|
|
17292
17414
|
try {
|
|
17293
|
-
parsed = parseLock(JSON.parse(await
|
|
17415
|
+
parsed = parseLock(JSON.parse(await readFile26(p, "utf8")));
|
|
17294
17416
|
} catch {
|
|
17295
17417
|
throw new Error(`Claim lockfile is malformed: ${p}`);
|
|
17296
17418
|
}
|
|
@@ -17394,20 +17516,20 @@ function matchOrphanStyle(prompt, manifest) {
|
|
|
17394
17516
|
return matchStyleByPattern(prompt, manifest);
|
|
17395
17517
|
}
|
|
17396
17518
|
async function loadSiblingManifests(ownManifestPath, workspaceManifestPaths, claimPaths) {
|
|
17397
|
-
const own =
|
|
17519
|
+
const own = path33.resolve(ownManifestPath);
|
|
17398
17520
|
const siblingManifestPaths = [
|
|
17399
17521
|
.../* @__PURE__ */ new Set([
|
|
17400
17522
|
...workspaceManifestPaths,
|
|
17401
|
-
...claimPaths.map((c) =>
|
|
17523
|
+
...claimPaths.map((c) => path33.join(path33.dirname(path33.resolve(c)), "pixelkiln.manifest.json"))
|
|
17402
17524
|
])
|
|
17403
17525
|
];
|
|
17404
17526
|
const siblings = [];
|
|
17405
17527
|
for (const siblingManifestPath of siblingManifestPaths) {
|
|
17406
|
-
if (
|
|
17407
|
-
if (!
|
|
17528
|
+
if (path33.resolve(siblingManifestPath) === own) continue;
|
|
17529
|
+
if (!existsSync26(siblingManifestPath)) continue;
|
|
17408
17530
|
try {
|
|
17409
17531
|
const { manifest } = await loadManifest(siblingManifestPath);
|
|
17410
|
-
siblings.push({ label:
|
|
17532
|
+
siblings.push({ label: path33.basename(path33.dirname(siblingManifestPath)), manifest });
|
|
17411
17533
|
} catch {
|
|
17412
17534
|
}
|
|
17413
17535
|
}
|
|
@@ -17527,8 +17649,8 @@ async function applyTags(provider, decisions, existing, opts = {}) {
|
|
|
17527
17649
|
}
|
|
17528
17650
|
|
|
17529
17651
|
// src/pick/salvage-server.ts
|
|
17530
|
-
import { mkdir as
|
|
17531
|
-
import
|
|
17652
|
+
import { mkdir as mkdir11, writeFile as writeFile14, readFile as readFile27 } from "fs/promises";
|
|
17653
|
+
import path34 from "path";
|
|
17532
17654
|
|
|
17533
17655
|
// src/pick/salvage-sheet.ts
|
|
17534
17656
|
var escapeHtml3 = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
@@ -17694,7 +17816,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
17694
17816
|
});
|
|
17695
17817
|
const html = renderSalvageSheet(orphans, {
|
|
17696
17818
|
styleId: ctx.styleId,
|
|
17697
|
-
importDir:
|
|
17819
|
+
importDir: path34.relative(process.cwd(), ctx.importDir) || "."
|
|
17698
17820
|
});
|
|
17699
17821
|
const byId = new Map(orphans.map((o) => [o.id, o]));
|
|
17700
17822
|
const existingTags = new Map(orphans.map((o) => [o.id, o.tags]));
|
|
@@ -17750,10 +17872,10 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
17750
17872
|
if (!buf.subarray(0, 8).equals(PNG_SIGNATURE2)) throw new Error("not a PNG");
|
|
17751
17873
|
decodePng(buf);
|
|
17752
17874
|
const assetId = idFromPrompt(orphan.prompt, taken);
|
|
17753
|
-
const rel =
|
|
17754
|
-
const outFile =
|
|
17755
|
-
await
|
|
17756
|
-
await
|
|
17875
|
+
const rel = path34.join("_salvaged", `${assetId}.png`);
|
|
17876
|
+
const outFile = path34.resolve(ctx.importDir, rel);
|
|
17877
|
+
await mkdir11(path34.dirname(outFile), { recursive: true });
|
|
17878
|
+
await writeFile14(outFile, buf);
|
|
17757
17879
|
ctx.manifest.assets[assetId] = {
|
|
17758
17880
|
prompt: orphan.prompt,
|
|
17759
17881
|
promptByStyle: {},
|
|
@@ -17785,7 +17907,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
17785
17907
|
sourceUrl: durableSource,
|
|
17786
17908
|
sourceUrls: durableSource ? [{ url: durableSource }] : [],
|
|
17787
17909
|
outputs: [{
|
|
17788
|
-
path: portableOutputPath(outFile,
|
|
17910
|
+
path: portableOutputPath(outFile, path34.dirname(ctx.manifestPath)),
|
|
17789
17911
|
sha256: sha256(buf)
|
|
17790
17912
|
}],
|
|
17791
17913
|
submittedAt: orphan.createdAt,
|
|
@@ -17809,9 +17931,9 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
17809
17931
|
}
|
|
17810
17932
|
}
|
|
17811
17933
|
await applyTags(provider, decisions, existingTags, { onProgress: log, characters: characterMembers });
|
|
17812
|
-
const raw = JSON.parse(await
|
|
17934
|
+
const raw = JSON.parse(await readFile27(ctx.manifestPath, "utf8"));
|
|
17813
17935
|
for (const id of importedAssetIds) raw.assets[id] = ctx.manifest.assets[id];
|
|
17814
|
-
await
|
|
17936
|
+
await writeFile14(ctx.manifestPath, JSON.stringify(raw, null, 2) + "\n");
|
|
17815
17937
|
await saveLock(ctx.lockPath, ctx.lock);
|
|
17816
17938
|
if (characterAssetIds.length) result.characterAssetIds = characterAssetIds;
|
|
17817
17939
|
return result;
|