pixelkiln 0.69.0 → 0.70.1
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 +660 -431
- package/dist/cli.js.map +1 -1
- package/dist/client/gallery.css +6 -1
- package/dist/client/gallery.js +2981 -2368
- package/dist/index.cjs +309 -142
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +196 -75
- package/dist/index.d.ts +196 -75
- package/dist/index.js +305 -139
- package/dist/index.js.map +1 -1
- package/docs/CLI.md +11 -0
- package/docs/REVISIONS.md +12 -0
- package/package.json +3 -2
- package/skills/pixelkiln/references/revisions.md +7 -4
package/dist/index.cjs
CHANGED
|
@@ -164,6 +164,7 @@ __export(index_exports, {
|
|
|
164
164
|
fontOutputPaths: () => fontOutputPaths,
|
|
165
165
|
formatCost: () => formatCost,
|
|
166
166
|
frameSetFps: () => frameSetFps,
|
|
167
|
+
galleryContentSecurityPolicy: () => galleryContentSecurityPolicy,
|
|
167
168
|
galleryMediaId: () => galleryMediaId,
|
|
168
169
|
galleryMediaRoute: () => galleryMediaRoute,
|
|
169
170
|
generateFont: () => generateFont,
|
|
@@ -2055,9 +2056,9 @@ var PixelLabClient = class {
|
|
|
2055
2056
|
apiKey;
|
|
2056
2057
|
http;
|
|
2057
2058
|
/** Retries transport failures, 429, and 5xx; see http.ts for the policy. */
|
|
2058
|
-
async request(
|
|
2059
|
+
async request(path35, init) {
|
|
2059
2060
|
const auth = this.apiKey.startsWith("Bearer ") ? this.apiKey : `Bearer ${this.apiKey}`;
|
|
2060
|
-
const res = await this.http(`${BASE}${
|
|
2061
|
+
const res = await this.http(`${BASE}${path35}`, {
|
|
2061
2062
|
...init,
|
|
2062
2063
|
headers: {
|
|
2063
2064
|
Authorization: auth,
|
|
@@ -2067,13 +2068,13 @@ var PixelLabClient = class {
|
|
|
2067
2068
|
});
|
|
2068
2069
|
const text = await res.text();
|
|
2069
2070
|
if (!res.ok) {
|
|
2070
|
-
throw new PixelLabError(`${init?.method ?? "GET"} ${
|
|
2071
|
+
throw new PixelLabError(`${init?.method ?? "GET"} ${path35} \u2192 ${res.status}`, res.status, text);
|
|
2071
2072
|
}
|
|
2072
2073
|
if (!text) return {};
|
|
2073
2074
|
try {
|
|
2074
2075
|
return JSON.parse(text);
|
|
2075
2076
|
} catch {
|
|
2076
|
-
throw new Error(`${init?.method ?? "GET"} ${
|
|
2077
|
+
throw new Error(`${init?.method ?? "GET"} ${path35} returned invalid JSON`);
|
|
2077
2078
|
}
|
|
2078
2079
|
}
|
|
2079
2080
|
async balance() {
|
|
@@ -2465,10 +2466,10 @@ var PixelLabClient = class {
|
|
|
2465
2466
|
const palette = args.paletteSwatchBase64 ? { color_image: { type: "base64", base64: args.paletteSwatchBase64, format: "png" }, force_colors: true } : {};
|
|
2466
2467
|
const encode = (image) => ({ type: "base64", base64: image.base64, format: image.format });
|
|
2467
2468
|
const south = args.reference?.south;
|
|
2468
|
-
let
|
|
2469
|
+
let path35;
|
|
2469
2470
|
let body;
|
|
2470
2471
|
if (args.mode === "v3") {
|
|
2471
|
-
|
|
2472
|
+
path35 = "/create-character-v3";
|
|
2472
2473
|
body = {
|
|
2473
2474
|
description: args.description,
|
|
2474
2475
|
image_size: imageSize,
|
|
@@ -2482,7 +2483,7 @@ var PixelLabClient = class {
|
|
|
2482
2483
|
...args.enhancePrompt ? { enhance_prompt: true } : {}
|
|
2483
2484
|
};
|
|
2484
2485
|
} else if (args.mode === "pro-flash") {
|
|
2485
|
-
|
|
2486
|
+
path35 = "/create-character-pro-flash";
|
|
2486
2487
|
const traits = args.styleTraits;
|
|
2487
2488
|
body = {
|
|
2488
2489
|
description: args.description,
|
|
@@ -2506,7 +2507,7 @@ var PixelLabClient = class {
|
|
|
2506
2507
|
} : {}
|
|
2507
2508
|
};
|
|
2508
2509
|
} else if (args.mode === "pro") {
|
|
2509
|
-
|
|
2510
|
+
path35 = "/create-character-pro";
|
|
2510
2511
|
body = {
|
|
2511
2512
|
description: args.description,
|
|
2512
2513
|
image_size: imageSize,
|
|
@@ -2520,7 +2521,7 @@ var PixelLabClient = class {
|
|
|
2520
2521
|
...!south && args.styleCharacterId ? { style_character_id: args.styleCharacterId } : {}
|
|
2521
2522
|
};
|
|
2522
2523
|
} else {
|
|
2523
|
-
|
|
2524
|
+
path35 = args.directions === 4 ? "/create-character-with-4-directions" : "/create-character-with-8-directions";
|
|
2524
2525
|
body = {
|
|
2525
2526
|
description: args.description,
|
|
2526
2527
|
image_size: imageSize,
|
|
@@ -2537,8 +2538,8 @@ var PixelLabClient = class {
|
|
|
2537
2538
|
...palette
|
|
2538
2539
|
};
|
|
2539
2540
|
}
|
|
2540
|
-
const raw = await this.request(
|
|
2541
|
-
return validateResponse(CharacterSubmitSchema, raw,
|
|
2541
|
+
const raw = await this.request(path35, { method: "POST", body: JSON.stringify(body) });
|
|
2542
|
+
return validateResponse(CharacterSubmitSchema, raw, path35.slice(1));
|
|
2542
2543
|
}
|
|
2543
2544
|
/** A text edit of an existing character, applied to every direction and stored as a sibling. */
|
|
2544
2545
|
async createCharacterState(args) {
|
|
@@ -3716,10 +3717,10 @@ var import_node_fs = require("fs");
|
|
|
3716
3717
|
function sha256(data) {
|
|
3717
3718
|
return (0, import_node_crypto.createHash)("sha256").update(data).digest("hex");
|
|
3718
3719
|
}
|
|
3719
|
-
async function sha256File(
|
|
3720
|
+
async function sha256File(path35) {
|
|
3720
3721
|
return new Promise((resolve, reject) => {
|
|
3721
3722
|
const hash = (0, import_node_crypto.createHash)("sha256");
|
|
3722
|
-
const stream = (0, import_node_fs.createReadStream)(
|
|
3723
|
+
const stream = (0, import_node_fs.createReadStream)(path35);
|
|
3723
3724
|
stream.on("data", (chunk2) => hash.update(chunk2));
|
|
3724
3725
|
stream.on("error", reject);
|
|
3725
3726
|
stream.on("end", () => resolve(hash.digest("hex")));
|
|
@@ -6082,9 +6083,9 @@ var RetroDiffusionClient = class {
|
|
|
6082
6083
|
if (!response.ok) throw new ProviderError("retrodiffusion", `Retro Diffusion download failed (${response.status})`, { status: response.status });
|
|
6083
6084
|
return Buffer.from(await response.arrayBuffer());
|
|
6084
6085
|
}
|
|
6085
|
-
async call(
|
|
6086
|
+
async call(path35, init = {}) {
|
|
6086
6087
|
if (!this.token) throw new Error("RD_API_KEY is not set");
|
|
6087
|
-
const response = await this.request(`${this.baseUrl}${
|
|
6088
|
+
const response = await this.request(`${this.baseUrl}${path35}`, {
|
|
6088
6089
|
...init,
|
|
6089
6090
|
headers: {
|
|
6090
6091
|
"Content-Type": "application/json",
|
|
@@ -12373,10 +12374,10 @@ function isSha256Hash(value) {
|
|
|
12373
12374
|
function parseCache(value) {
|
|
12374
12375
|
return HashCacheSchema.parse(value);
|
|
12375
12376
|
}
|
|
12376
|
-
async function loadCache(
|
|
12377
|
-
if (!(0, import_node_fs14.existsSync)(
|
|
12377
|
+
async function loadCache(path35) {
|
|
12378
|
+
if (!(0, import_node_fs14.existsSync)(path35)) return { version: 1, hashes: {} };
|
|
12378
12379
|
try {
|
|
12379
|
-
const parsed = HashCacheSchema.safeParse(JSON.parse(await (0, import_promises11.readFile)(
|
|
12380
|
+
const parsed = HashCacheSchema.safeParse(JSON.parse(await (0, import_promises11.readFile)(path35, "utf8")));
|
|
12380
12381
|
if (!parsed.success) return { version: 1, hashes: {} };
|
|
12381
12382
|
return {
|
|
12382
12383
|
version: 1,
|
|
@@ -12388,18 +12389,18 @@ async function loadCache(path34) {
|
|
|
12388
12389
|
return { version: 1, hashes: {} };
|
|
12389
12390
|
}
|
|
12390
12391
|
}
|
|
12391
|
-
async function saveCache(
|
|
12392
|
+
async function saveCache(path35, cache) {
|
|
12392
12393
|
const sorted = {};
|
|
12393
12394
|
for (const key of Object.keys(cache.hashes).sort()) {
|
|
12394
12395
|
const hash = cache.hashes[key];
|
|
12395
12396
|
if (!isSha256Hash(hash)) throw new Error(`Refusing to cache invalid SHA-256 for ${key}`);
|
|
12396
12397
|
sorted[key] = hash;
|
|
12397
12398
|
}
|
|
12398
|
-
await (0, import_promises11.mkdir)(import_node_path18.default.dirname(import_node_path18.default.resolve(
|
|
12399
|
-
const tmp = `${
|
|
12399
|
+
await (0, import_promises11.mkdir)(import_node_path18.default.dirname(import_node_path18.default.resolve(path35)), { recursive: true });
|
|
12400
|
+
const tmp = `${path35}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
12400
12401
|
try {
|
|
12401
12402
|
await (0, import_promises11.writeFile)(tmp, JSON.stringify({ version: 1, hashes: sorted }, null, 2) + "\n");
|
|
12402
|
-
await (0, import_promises11.rename)(tmp,
|
|
12403
|
+
await (0, import_promises11.rename)(tmp, path35);
|
|
12403
12404
|
} finally {
|
|
12404
12405
|
await (0, import_promises11.rm)(tmp, { force: true });
|
|
12405
12406
|
}
|
|
@@ -15204,9 +15205,9 @@ async function runPicker(provider, lock, lockPath, opts = {}) {
|
|
|
15204
15205
|
|
|
15205
15206
|
// src/gallery/snapshot.ts
|
|
15206
15207
|
var import_node_crypto6 = require("crypto");
|
|
15207
|
-
var
|
|
15208
|
-
var
|
|
15209
|
-
var
|
|
15208
|
+
var import_node_fs26 = require("fs");
|
|
15209
|
+
var import_promises23 = require("fs/promises");
|
|
15210
|
+
var import_node_path31 = __toESM(require("path"), 1);
|
|
15210
15211
|
|
|
15211
15212
|
// src/workspace.ts
|
|
15212
15213
|
var import_promises19 = require("fs/promises");
|
|
@@ -15342,8 +15343,8 @@ function validateWorkspace(ws, dir) {
|
|
|
15342
15343
|
}
|
|
15343
15344
|
|
|
15344
15345
|
// src/gallery/edit.ts
|
|
15345
|
-
var
|
|
15346
|
-
var
|
|
15346
|
+
var import_node_path30 = __toESM(require("path"), 1);
|
|
15347
|
+
var import_zod11 = require("zod");
|
|
15347
15348
|
|
|
15348
15349
|
// src/manifest-edit.ts
|
|
15349
15350
|
var import_promises20 = require("fs/promises");
|
|
@@ -15391,15 +15392,27 @@ var NewAssetSchema = import_zod8.z.object({
|
|
|
15391
15392
|
/**
|
|
15392
15393
|
* A controlled regeneration of another asset in the same manifest,
|
|
15393
15394
|
* instead of a fresh generation. `inpaint` needs a mask upload the
|
|
15394
|
-
* gallery does not offer yet, so only `image-to-image`
|
|
15395
|
-
*
|
|
15395
|
+
* gallery does not offer yet, so only `image-to-image` and
|
|
15396
|
+
* `animate-skeleton` (whose keypoints file the gallery writes first) can
|
|
15397
|
+
* be created here; hand-edit the manifest for the other modes.
|
|
15396
15398
|
*/
|
|
15397
|
-
revision: import_zod8.z.
|
|
15398
|
-
|
|
15399
|
-
|
|
15400
|
-
|
|
15401
|
-
|
|
15402
|
-
|
|
15399
|
+
revision: import_zod8.z.discriminatedUnion("mode", [
|
|
15400
|
+
import_zod8.z.object({
|
|
15401
|
+
mode: import_zod8.z.literal("image-to-image"),
|
|
15402
|
+
/** Asset id of the parent this revises; must already exist. */
|
|
15403
|
+
from: import_zod8.z.string().min(1),
|
|
15404
|
+
strength: import_zod8.z.number().min(0).max(1).optional()
|
|
15405
|
+
}).strict(),
|
|
15406
|
+
import_zod8.z.object({
|
|
15407
|
+
mode: import_zod8.z.literal("animate-skeleton"),
|
|
15408
|
+
from: import_zod8.z.string().min(1),
|
|
15409
|
+
/** Manifest-relative; checked when the manifest is loaded, like every revision input. */
|
|
15410
|
+
keypointsFile: import_zod8.z.string().min(1),
|
|
15411
|
+
direction: CharacterDirectionSchema,
|
|
15412
|
+
description: import_zod8.z.string().min(1).optional(),
|
|
15413
|
+
skeletonTemplate: import_zod8.z.string().min(1).optional()
|
|
15414
|
+
}).strict()
|
|
15415
|
+
]).optional(),
|
|
15403
15416
|
/** A pose or outfit of an existing `character`/`objectPro` base or state. */
|
|
15404
15417
|
state: NewStateSchema.optional(),
|
|
15405
15418
|
/** A loop of an existing `character`/`objectPro` base or state. */
|
|
@@ -15903,41 +15916,118 @@ function openInEditor(file, editor = process.env.PIXELKILN_EDITOR) {
|
|
|
15903
15916
|
return openExternal(file, command);
|
|
15904
15917
|
}
|
|
15905
15918
|
|
|
15919
|
+
// src/gallery/skeleton.ts
|
|
15920
|
+
var import_node_fs25 = require("fs");
|
|
15921
|
+
var import_promises22 = require("fs/promises");
|
|
15922
|
+
var import_node_path29 = __toESM(require("path"), 1);
|
|
15923
|
+
var import_zod10 = require("zod");
|
|
15924
|
+
var SkeletonEstimateRequestSchema = import_zod10.z.object({
|
|
15925
|
+
project: import_zod10.z.string().min(1).optional(),
|
|
15926
|
+
styleId: import_zod10.z.string().min(1),
|
|
15927
|
+
assetId: import_zod10.z.string().min(1),
|
|
15928
|
+
frames: import_zod10.z.number().int().min(3).max(15).optional()
|
|
15929
|
+
}).strict();
|
|
15930
|
+
var SkeletonAnimationRequestSchema = import_zod10.z.object({
|
|
15931
|
+
action: import_zod10.z.literal("create-skeleton-animation"),
|
|
15932
|
+
project: import_zod10.z.string().min(1).optional(),
|
|
15933
|
+
expectedSha256: import_zod10.z.string().regex(/^[0-9a-f]{64}$/),
|
|
15934
|
+
styleId: import_zod10.z.string().min(1),
|
|
15935
|
+
/** The sprite the animation poses. */
|
|
15936
|
+
from: import_zod10.z.string().min(1),
|
|
15937
|
+
assetId: import_zod10.z.string().min(1).regex(/^[^/\\]+$/, "asset ids cannot contain slashes"),
|
|
15938
|
+
/** The motion, sent as PixelLab's `action`. */
|
|
15939
|
+
prompt: import_zod10.z.string().min(1),
|
|
15940
|
+
direction: CharacterDirectionSchema,
|
|
15941
|
+
description: import_zod10.z.string().min(1).optional(),
|
|
15942
|
+
skeletonTemplate: import_zod10.z.string().min(1).optional(),
|
|
15943
|
+
/** Manifest-relative JSON path inside the project. */
|
|
15944
|
+
keypointsFile: import_zod10.z.string().min(1),
|
|
15945
|
+
/** Poses to write to `keypointsFile`; omitted to use a file that already exists. */
|
|
15946
|
+
set: SkeletonSetSchema.optional(),
|
|
15947
|
+
/** Replace an existing `keypointsFile` with `set`. */
|
|
15948
|
+
overwrite: import_zod10.z.boolean().optional()
|
|
15949
|
+
}).strict();
|
|
15950
|
+
async function createSkeletonAnimation(manifestPath, request) {
|
|
15951
|
+
const root = import_node_path29.default.dirname(import_node_path29.default.resolve(manifestPath));
|
|
15952
|
+
const file = import_node_path29.default.resolve(root, request.keypointsFile);
|
|
15953
|
+
const relative = import_node_path29.default.relative(root, file);
|
|
15954
|
+
if (!request.keypointsFile.endsWith(".json") || relative.startsWith("..") || import_node_path29.default.isAbsolute(relative)) {
|
|
15955
|
+
throw new ManifestEditError("the keypoints file must be a .json path inside the project");
|
|
15956
|
+
}
|
|
15957
|
+
const existed = (0, import_node_fs25.existsSync)(file);
|
|
15958
|
+
let previous = null;
|
|
15959
|
+
if (request.set) {
|
|
15960
|
+
if (existed && !request.overwrite) {
|
|
15961
|
+
throw new ManifestEditError(`${request.keypointsFile} already exists; choose another path, or replace it`);
|
|
15962
|
+
}
|
|
15963
|
+
if (existed) previous = await (0, import_promises22.readFile)(file);
|
|
15964
|
+
await (0, import_promises22.mkdir)(import_node_path29.default.dirname(file), { recursive: true });
|
|
15965
|
+
await (0, import_promises22.writeFile)(file, JSON.stringify(request.set, null, 2) + "\n");
|
|
15966
|
+
} else if (!existed) {
|
|
15967
|
+
throw new ManifestEditError(`${request.keypointsFile} does not exist; paste poses or estimate them from the sprite`);
|
|
15968
|
+
}
|
|
15969
|
+
try {
|
|
15970
|
+
await applyManifestEdit(manifestPath, {
|
|
15971
|
+
action: "add-asset",
|
|
15972
|
+
assetId: request.assetId,
|
|
15973
|
+
expectedSha256: request.expectedSha256,
|
|
15974
|
+
asset: {
|
|
15975
|
+
prompt: request.prompt,
|
|
15976
|
+
styles: [request.styleId],
|
|
15977
|
+
revision: {
|
|
15978
|
+
mode: "animate-skeleton",
|
|
15979
|
+
from: request.from,
|
|
15980
|
+
keypointsFile: relative.split(import_node_path29.default.sep).join("/"),
|
|
15981
|
+
direction: request.direction,
|
|
15982
|
+
...request.description ? { description: request.description } : {},
|
|
15983
|
+
...request.skeletonTemplate ? { skeletonTemplate: request.skeletonTemplate } : {}
|
|
15984
|
+
}
|
|
15985
|
+
}
|
|
15986
|
+
});
|
|
15987
|
+
} catch (error) {
|
|
15988
|
+
if (request.set) {
|
|
15989
|
+
if (previous) await (0, import_promises22.writeFile)(file, previous);
|
|
15990
|
+
else await (0, import_promises22.rm)(file, { force: true });
|
|
15991
|
+
}
|
|
15992
|
+
throw error;
|
|
15993
|
+
}
|
|
15994
|
+
}
|
|
15995
|
+
|
|
15906
15996
|
// src/gallery/edit.ts
|
|
15907
|
-
var Base64 =
|
|
15908
|
-
var HandEditRequestSchema =
|
|
15909
|
-
|
|
15910
|
-
action:
|
|
15911
|
-
project:
|
|
15912
|
-
assetId:
|
|
15913
|
-
styleId:
|
|
15914
|
-
expectedSha256:
|
|
15997
|
+
var Base64 = import_zod11.z.string().regex(/^[A-Za-z0-9+/]*={0,2}$/).max(Math.ceil(MAX_HAND_EDIT_BYTES / 3) * 4);
|
|
15998
|
+
var HandEditRequestSchema = import_zod11.z.discriminatedUnion("action", [
|
|
15999
|
+
import_zod11.z.object({
|
|
16000
|
+
action: import_zod11.z.literal("start-edit"),
|
|
16001
|
+
project: import_zod11.z.string().min(1).optional(),
|
|
16002
|
+
assetId: import_zod11.z.string().min(1),
|
|
16003
|
+
styleId: import_zod11.z.string().min(1),
|
|
16004
|
+
expectedSha256: import_zod11.z.string().regex(/^[0-9a-f]{64}$/),
|
|
15915
16005
|
/** Also launch the author's editor on the file. */
|
|
15916
|
-
open:
|
|
16006
|
+
open: import_zod11.z.boolean().optional()
|
|
15917
16007
|
}).strict(),
|
|
15918
|
-
|
|
15919
|
-
action:
|
|
15920
|
-
project:
|
|
15921
|
-
assetId:
|
|
15922
|
-
styleId:
|
|
15923
|
-
expectedSha256:
|
|
16008
|
+
import_zod11.z.object({
|
|
16009
|
+
action: import_zod11.z.literal("detach-edit"),
|
|
16010
|
+
project: import_zod11.z.string().min(1).optional(),
|
|
16011
|
+
assetId: import_zod11.z.string().min(1),
|
|
16012
|
+
styleId: import_zod11.z.string().min(1),
|
|
16013
|
+
expectedSha256: import_zod11.z.string().regex(/^[0-9a-f]{64}$/)
|
|
15924
16014
|
}).strict(),
|
|
15925
16015
|
/** From the in-browser editor: the page holds the bytes, the server validates and writes. */
|
|
15926
|
-
|
|
15927
|
-
action:
|
|
15928
|
-
project:
|
|
15929
|
-
assetId:
|
|
15930
|
-
styleId:
|
|
15931
|
-
expectedSha256:
|
|
16016
|
+
import_zod11.z.object({
|
|
16017
|
+
action: import_zod11.z.literal("save-edit"),
|
|
16018
|
+
project: import_zod11.z.string().min(1).optional(),
|
|
16019
|
+
assetId: import_zod11.z.string().min(1),
|
|
16020
|
+
styleId: import_zod11.z.string().min(1),
|
|
16021
|
+
expectedSha256: import_zod11.z.string().regex(/^[0-9a-f]{64}$/),
|
|
15932
16022
|
/** The flattened image; for a frame set, `frames` instead. */
|
|
15933
16023
|
png: Base64.min(1).optional(),
|
|
15934
16024
|
/** Every frame of a set, tagged with the role it was opened under (null for one added in the editor). */
|
|
15935
|
-
frames:
|
|
16025
|
+
frames: import_zod11.z.array(import_zod11.z.object({ role: import_zod11.z.string().min(1).nullable(), png: Base64.min(1) }).strict()).min(1).max(256).optional(),
|
|
15936
16026
|
/** The editor's layered project file, kept beside the edit. */
|
|
15937
16027
|
pxo: Base64.optional(),
|
|
15938
16028
|
/** Editor identity from the bridge's ready message. */
|
|
15939
|
-
editor:
|
|
15940
|
-
protocol:
|
|
16029
|
+
editor: import_zod11.z.string().min(1).max(200),
|
|
16030
|
+
protocol: import_zod11.z.number().int().positive()
|
|
15941
16031
|
}).strict()
|
|
15942
16032
|
]);
|
|
15943
16033
|
function createGalleryEditHandler(opts) {
|
|
@@ -15978,6 +16068,23 @@ function createGalleryEditHandler(opts) {
|
|
|
15978
16068
|
}
|
|
15979
16069
|
return opts.reload();
|
|
15980
16070
|
}
|
|
16071
|
+
const skeleton = SkeletonAnimationRequestSchema.safeParse(body);
|
|
16072
|
+
if (skeleton.success) {
|
|
16073
|
+
let manifestPath2;
|
|
16074
|
+
try {
|
|
16075
|
+
manifestPath2 = await opts.manifestFor(skeleton.data.project);
|
|
16076
|
+
} catch (error) {
|
|
16077
|
+
throw new ManifestEditError(error instanceof Error ? error.message : String(error));
|
|
16078
|
+
}
|
|
16079
|
+
await createSkeletonAnimation(manifestPath2, skeleton.data);
|
|
16080
|
+
log(` manifest edited: added skeleton animation ${skeleton.data.assetId} (${skeleton.data.keypointsFile})`);
|
|
16081
|
+
return opts.reload();
|
|
16082
|
+
}
|
|
16083
|
+
if (body?.action === "create-skeleton-animation") {
|
|
16084
|
+
throw new ManifestEditError(
|
|
16085
|
+
"invalid skeleton animation: " + skeleton.error.issues.slice(0, 3).map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; ")
|
|
16086
|
+
);
|
|
16087
|
+
}
|
|
15981
16088
|
const parsed = ManifestEditSchema.safeParse(body);
|
|
15982
16089
|
if (!parsed.success) {
|
|
15983
16090
|
throw new ManifestEditError(
|
|
@@ -15992,7 +16099,7 @@ function createGalleryEditHandler(opts) {
|
|
|
15992
16099
|
}
|
|
15993
16100
|
const result = await applyManifestEdit(manifestPath, parsed.data);
|
|
15994
16101
|
if (result.changed) {
|
|
15995
|
-
const relative =
|
|
16102
|
+
const relative = import_node_path30.default.relative(process.cwd(), result.manifestPath);
|
|
15996
16103
|
const shown = !relative || relative.startsWith("..") ? result.manifestPath : relative;
|
|
15997
16104
|
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;
|
|
15998
16105
|
log(` manifest edited: ${parsed.data.action === "add-asset" ? "added" : "changed"} ${subject} in ${shown}`);
|
|
@@ -16004,7 +16111,7 @@ function createGalleryEditHandler(opts) {
|
|
|
16004
16111
|
// src/gallery/snapshot.ts
|
|
16005
16112
|
var PROJECT_FILE_TYPE = "application/octet-stream";
|
|
16006
16113
|
function galleryMediaId(absolutePath) {
|
|
16007
|
-
return (0, import_node_crypto6.createHash)("sha256").update(
|
|
16114
|
+
return (0, import_node_crypto6.createHash)("sha256").update(import_node_path31.default.resolve(absolutePath)).digest("hex").slice(0, 24);
|
|
16008
16115
|
}
|
|
16009
16116
|
function galleryMediaRoute(id) {
|
|
16010
16117
|
return `/media/${id}`;
|
|
@@ -16023,9 +16130,9 @@ function describeHistory(media, entry, cacheDir) {
|
|
|
16023
16130
|
return (entry.history ?? []).map((generation, i) => {
|
|
16024
16131
|
const outputs = generation.outputs.map((output) => {
|
|
16025
16132
|
const mediaType = output.mediaType ?? MediaType.PNG;
|
|
16026
|
-
const file =
|
|
16133
|
+
const file = import_node_path31.default.join(cacheDir, cacheFileName(output.sha256, mediaType));
|
|
16027
16134
|
let url = null;
|
|
16028
|
-
if ((0,
|
|
16135
|
+
if ((0, import_node_fs26.existsSync)(file)) {
|
|
16029
16136
|
const id = galleryMediaId(file);
|
|
16030
16137
|
media.set(id, { path: file, contentType: mediaType });
|
|
16031
16138
|
url = `${galleryMediaRoute(id)}?v=${output.sha256.slice(0, 24)}`;
|
|
@@ -16057,7 +16164,7 @@ function describeHistory(media, entry, cacheDir) {
|
|
|
16057
16164
|
var MAX_COMPARED_BYTES = 8 * 1024 * 1024;
|
|
16058
16165
|
async function samePixels(a, b) {
|
|
16059
16166
|
try {
|
|
16060
|
-
const [left, right] = await Promise.all([(0,
|
|
16167
|
+
const [left, right] = await Promise.all([(0, import_promises23.readFile)(a), (0, import_promises23.readFile)(b)]);
|
|
16061
16168
|
if (left.length > MAX_COMPARED_BYTES || right.length > MAX_COMPARED_BYTES) return false;
|
|
16062
16169
|
const x = decodePng(left);
|
|
16063
16170
|
const y = decodePng(right);
|
|
@@ -16123,7 +16230,7 @@ function describeCharacter(spec, entry) {
|
|
|
16123
16230
|
}
|
|
16124
16231
|
async function fileInfo(absolutePath) {
|
|
16125
16232
|
try {
|
|
16126
|
-
const info = await (0,
|
|
16233
|
+
const info = await (0, import_promises23.stat)(absolutePath);
|
|
16127
16234
|
return info.isFile() ? { bytes: info.size, modifiedAt: info.mtime.toISOString() } : null;
|
|
16128
16235
|
} catch {
|
|
16129
16236
|
return null;
|
|
@@ -16155,10 +16262,10 @@ async function describeOutput(media, root, absolutePath, recorded = {}) {
|
|
|
16155
16262
|
async function describeQuality(media, root, inspection) {
|
|
16156
16263
|
const outputPaths = inspection.outputs ?? [inspection.output];
|
|
16157
16264
|
const outputs = await Promise.all(
|
|
16158
|
-
outputPaths.map((file) => describeOutput(media, root,
|
|
16265
|
+
outputPaths.map((file) => describeOutput(media, root, import_node_path31.default.resolve(root, file)))
|
|
16159
16266
|
);
|
|
16160
|
-
const recordPath =
|
|
16161
|
-
const recordExists = (0,
|
|
16267
|
+
const recordPath = import_node_path31.default.resolve(root, inspection.record);
|
|
16268
|
+
const recordExists = (0, import_node_fs26.existsSync)(recordPath);
|
|
16162
16269
|
let options = null;
|
|
16163
16270
|
let check = null;
|
|
16164
16271
|
if (recordExists) {
|
|
@@ -16177,7 +16284,7 @@ async function describeQuality(media, root, inspection) {
|
|
|
16177
16284
|
return {
|
|
16178
16285
|
state: inspection.state,
|
|
16179
16286
|
reason: inspection.reason,
|
|
16180
|
-
output: portableOutputPath(
|
|
16287
|
+
output: portableOutputPath(import_node_path31.default.resolve(root, inspection.output), root),
|
|
16181
16288
|
record: portableOutputPath(recordPath, root),
|
|
16182
16289
|
recordExists,
|
|
16183
16290
|
outputs,
|
|
@@ -16215,9 +16322,9 @@ async function buildGallerySnapshot(opts) {
|
|
|
16215
16322
|
const media = /* @__PURE__ */ new Map();
|
|
16216
16323
|
const plan = await buildPlan(specs, lock);
|
|
16217
16324
|
const items = [];
|
|
16218
|
-
const manifestText = await (0,
|
|
16325
|
+
const manifestText = await (0, import_promises23.readFile)(loaded.path, "utf8");
|
|
16219
16326
|
const manifestSha256 = sha256(manifestText);
|
|
16220
|
-
const cacheDir =
|
|
16327
|
+
const cacheDir = import_node_path31.default.resolve(import_node_path31.default.dirname(opts.lockPath), ".pixelkiln", "cache");
|
|
16221
16328
|
let rawStyles = {};
|
|
16222
16329
|
try {
|
|
16223
16330
|
const raw = JSON.parse(manifestText);
|
|
@@ -16241,12 +16348,12 @@ async function buildGallerySnapshot(opts) {
|
|
|
16241
16348
|
)
|
|
16242
16349
|
);
|
|
16243
16350
|
if (spec.source) {
|
|
16244
|
-
const editPath =
|
|
16351
|
+
const editPath = import_node_path31.default.resolve(root, spec.source);
|
|
16245
16352
|
const memberCount = sourceIsStem(spec.source, entry, root) ? entry.outputs.length : 1;
|
|
16246
16353
|
const editShas = await Promise.all(
|
|
16247
16354
|
Array.from({ length: memberCount }, async (_, index) => {
|
|
16248
16355
|
const file = sourceOutputPath(spec.source, entry, index, root);
|
|
16249
|
-
return { file, sha256: (0,
|
|
16356
|
+
return { file, sha256: (0, import_node_fs26.existsSync)(file) ? await sha256File(file) : null };
|
|
16250
16357
|
})
|
|
16251
16358
|
);
|
|
16252
16359
|
edits = await Promise.all(editShas.map(({ file, sha256: editSha }) => describeOutput(media, root, file, { sha256: editSha })));
|
|
@@ -16254,7 +16361,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
16254
16361
|
const companion = await readHandEditCompanion(editPath);
|
|
16255
16362
|
if (companion) {
|
|
16256
16363
|
const projectPath = handEditProjectPath(editPath);
|
|
16257
|
-
const project = companion.project && (0,
|
|
16364
|
+
const project = companion.project && (0, import_node_fs26.existsSync)(projectPath) ? await fileInfo(projectPath) : null;
|
|
16258
16365
|
let projectUrl = null;
|
|
16259
16366
|
if (project) {
|
|
16260
16367
|
const id = galleryMediaId(projectPath);
|
|
@@ -16290,8 +16397,8 @@ async function buildGallerySnapshot(opts) {
|
|
|
16290
16397
|
editStatus = edits.some((member) => !member.exists) ? "missing" : editChanged.every((changed) => !changed) ? "same" : regenerated ? "regenerated-since" : "edited";
|
|
16291
16398
|
}
|
|
16292
16399
|
} else if (spec.source) {
|
|
16293
|
-
outputs = [await describeOutput(media, root,
|
|
16294
|
-
} else if (!entry && (0,
|
|
16400
|
+
outputs = [await describeOutput(media, root, import_node_path31.default.resolve(root, spec.source))];
|
|
16401
|
+
} else if (!entry && (0, import_node_fs26.existsSync)(spec.outFile)) {
|
|
16295
16402
|
outputs = [await describeOutput(media, root, spec.outFile)];
|
|
16296
16403
|
} else {
|
|
16297
16404
|
outputs = [];
|
|
@@ -16332,6 +16439,10 @@ async function buildGallerySnapshot(opts) {
|
|
|
16332
16439
|
revision: entry?.revision ?? null,
|
|
16333
16440
|
revisionParentKey: spec.revision ? lockKey(spec.styleId, spec.revision.sourceAssetId) : entry?.revision ? lockKey(spec.styleId, entry.revision.sourceAssetId) : null,
|
|
16334
16441
|
mirrorOfKey: spec.mirror ? lockKey(spec.styleId, spec.mirror.sourceAssetId) : entry?.mirror ? lockKey(spec.styleId, entry.mirror.sourceAssetId) : null,
|
|
16442
|
+
skeleton: spec.revision?.mode === "animate-skeleton" && spec.revision.keypointsFile ? {
|
|
16443
|
+
keypointsFile: portableOutputPath(spec.revision.keypointsFile, root),
|
|
16444
|
+
set: spec.revision.skeleton ?? null
|
|
16445
|
+
} : null,
|
|
16335
16446
|
character: describeCharacter(spec, entry),
|
|
16336
16447
|
outputs,
|
|
16337
16448
|
quality,
|
|
@@ -16396,6 +16507,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
16396
16507
|
revision: entry.revision,
|
|
16397
16508
|
revisionParentKey: entry.revision ? lockKey(entry.styleId, entry.revision.sourceAssetId) : null,
|
|
16398
16509
|
mirrorOfKey: entry.mirror ? lockKey(entry.styleId, entry.mirror.sourceAssetId) : null,
|
|
16510
|
+
skeleton: null,
|
|
16399
16511
|
character: describeCharacter(void 0, entry),
|
|
16400
16512
|
outputs,
|
|
16401
16513
|
quality: null,
|
|
@@ -16486,7 +16598,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
16486
16598
|
id: loaded.manifest.name,
|
|
16487
16599
|
name: loaded.manifest.name,
|
|
16488
16600
|
manifest: loaded.path,
|
|
16489
|
-
lock:
|
|
16601
|
+
lock: import_node_path31.default.resolve(opts.lockPath),
|
|
16490
16602
|
root,
|
|
16491
16603
|
provider: loaded.manifest.provider,
|
|
16492
16604
|
account: null,
|
|
@@ -16512,7 +16624,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
16512
16624
|
return { snapshot, media };
|
|
16513
16625
|
}
|
|
16514
16626
|
async function buildWorkspaceGallerySnapshot(opts) {
|
|
16515
|
-
const dir =
|
|
16627
|
+
const dir = import_node_path31.default.dirname(import_node_path31.default.resolve(opts.workspacePath));
|
|
16516
16628
|
const media = /* @__PURE__ */ new Map();
|
|
16517
16629
|
const projects = [];
|
|
16518
16630
|
const items = [];
|
|
@@ -16555,11 +16667,11 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
16555
16667
|
id: project.id,
|
|
16556
16668
|
name: loaded.manifest.name,
|
|
16557
16669
|
manifest: manifestPath,
|
|
16558
|
-
lock:
|
|
16670
|
+
lock: import_node_path31.default.resolve(lockPath),
|
|
16559
16671
|
root: loaded.root,
|
|
16560
16672
|
provider: loaded.manifest.provider,
|
|
16561
16673
|
account: project.account ?? null,
|
|
16562
|
-
manifestSha256: manifestSha256 ?? sha256(await (0,
|
|
16674
|
+
manifestSha256: manifestSha256 ?? sha256(await (0, import_promises23.readFile)(manifestPath, "utf8")),
|
|
16563
16675
|
historyLimit: historyLimit(loaded.manifest),
|
|
16564
16676
|
entries: Object.keys(lock.entries).length,
|
|
16565
16677
|
items: projectItems.length,
|
|
@@ -16571,8 +16683,8 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
16571
16683
|
id: project.id,
|
|
16572
16684
|
name: project.id,
|
|
16573
16685
|
manifest: manifestPath,
|
|
16574
|
-
lock:
|
|
16575
|
-
root:
|
|
16686
|
+
lock: import_node_path31.default.resolve(lockPath),
|
|
16687
|
+
root: import_node_path31.default.dirname(manifestPath),
|
|
16576
16688
|
provider: project.provider,
|
|
16577
16689
|
account: project.account ?? null,
|
|
16578
16690
|
manifestSha256: null,
|
|
@@ -16599,7 +16711,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
16599
16711
|
version: 1,
|
|
16600
16712
|
generatedAt: (opts.now ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
|
|
16601
16713
|
project: null,
|
|
16602
|
-
workspace: { path:
|
|
16714
|
+
workspace: { path: import_node_path31.default.resolve(opts.workspacePath), dir, projects },
|
|
16603
16715
|
filter,
|
|
16604
16716
|
totals: {
|
|
16605
16717
|
entries: projects.reduce((sum, project) => sum + project.entries, 0),
|
|
@@ -16616,13 +16728,48 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
16616
16728
|
}
|
|
16617
16729
|
|
|
16618
16730
|
// src/gallery/page.ts
|
|
16619
|
-
var
|
|
16731
|
+
var import_node_fs27 = require("fs");
|
|
16732
|
+
|
|
16733
|
+
// src/gallery/client-bundle.ts
|
|
16734
|
+
var import_node_module = require("module");
|
|
16735
|
+
var import_node_url2 = require("url");
|
|
16736
|
+
function bundleGalleryClient(entry = (0, import_node_url2.fileURLToPath)(new URL("./client/src/main.ts", importMetaUrl))) {
|
|
16737
|
+
const esbuild = (0, import_node_module.createRequire)(importMetaUrl)("esbuild");
|
|
16738
|
+
const result = esbuild.buildSync({
|
|
16739
|
+
entryPoints: [entry],
|
|
16740
|
+
bundle: true,
|
|
16741
|
+
format: "iife",
|
|
16742
|
+
target: "es2020",
|
|
16743
|
+
platform: "browser",
|
|
16744
|
+
write: false,
|
|
16745
|
+
legalComments: "none",
|
|
16746
|
+
charset: "utf8"
|
|
16747
|
+
});
|
|
16748
|
+
return result.outputFiles[0].text;
|
|
16749
|
+
}
|
|
16750
|
+
|
|
16751
|
+
// src/gallery/page.ts
|
|
16752
|
+
function galleryContentSecurityPolicy(nonce) {
|
|
16753
|
+
return [
|
|
16754
|
+
"default-src 'self'",
|
|
16755
|
+
`script-src 'nonce-${nonce}'`,
|
|
16756
|
+
`style-src 'nonce-${nonce}'`,
|
|
16757
|
+
"img-src 'self' data: blob:",
|
|
16758
|
+
"frame-src 'self'",
|
|
16759
|
+
"connect-src 'self'",
|
|
16760
|
+
"object-src 'none'",
|
|
16761
|
+
"base-uri 'none'",
|
|
16762
|
+
"form-action 'none'",
|
|
16763
|
+
"frame-ancestors 'none'"
|
|
16764
|
+
].join("; ");
|
|
16765
|
+
}
|
|
16620
16766
|
var CLIENT_DIR = new URL("./client/", importMetaUrl);
|
|
16621
16767
|
var client;
|
|
16622
16768
|
function clientAssets() {
|
|
16769
|
+
const prebuilt = new URL("gallery.js", CLIENT_DIR);
|
|
16623
16770
|
client ??= {
|
|
16624
|
-
css: (0,
|
|
16625
|
-
js: (0,
|
|
16771
|
+
css: (0, import_node_fs27.readFileSync)(new URL("gallery.css", CLIENT_DIR), "utf8"),
|
|
16772
|
+
js: (0, import_node_fs27.existsSync)(prebuilt) ? (0, import_node_fs27.readFileSync)(prebuilt, "utf8") : bundleGalleryClient()
|
|
16626
16773
|
};
|
|
16627
16774
|
return client;
|
|
16628
16775
|
}
|
|
@@ -16634,6 +16781,7 @@ function renderGallery(snapshot, opts = {}) {
|
|
|
16634
16781
|
const editor = JSON.stringify(Boolean(opts.editor && opts.session));
|
|
16635
16782
|
const title = `${snapshot.project?.name ?? "workspace"} | pixelkiln`;
|
|
16636
16783
|
const { css, js } = clientAssets();
|
|
16784
|
+
const nonce = opts.nonce ? ` nonce="${escapeHtml2(opts.nonce)}"` : "";
|
|
16637
16785
|
return `<!doctype html>
|
|
16638
16786
|
<html lang="en">
|
|
16639
16787
|
<head>
|
|
@@ -16641,7 +16789,7 @@ function renderGallery(snapshot, opts = {}) {
|
|
|
16641
16789
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
16642
16790
|
<title>${escapeHtml2(title)}</title>
|
|
16643
16791
|
<link rel="icon" href="data:,">
|
|
16644
|
-
<style>
|
|
16792
|
+
<style${nonce}>
|
|
16645
16793
|
${css}</style>
|
|
16646
16794
|
</head>
|
|
16647
16795
|
<body>
|
|
@@ -16683,7 +16831,7 @@ ${css}</style>
|
|
|
16683
16831
|
<div id="drawer-host"></div>
|
|
16684
16832
|
<div id="dialog-host"></div>
|
|
16685
16833
|
<datalist id="view-options"><option value="low top-down"><option value="high top-down"><option value="side"><option value="sidescroller"></datalist>
|
|
16686
|
-
<script>
|
|
16834
|
+
<script${nonce}>
|
|
16687
16835
|
const INITIAL = ${data};
|
|
16688
16836
|
const SESSION = ${session};
|
|
16689
16837
|
const EDITABLE = ${editable};
|
|
@@ -16748,10 +16896,10 @@ var EDITOR_RELEASE_BASE = "https://github.com/gfargo/pixelkiln/releases/download
|
|
|
16748
16896
|
|
|
16749
16897
|
// src/editor/install.ts
|
|
16750
16898
|
var import_node_crypto7 = require("crypto");
|
|
16751
|
-
var
|
|
16752
|
-
var
|
|
16899
|
+
var import_node_fs28 = require("fs");
|
|
16900
|
+
var import_promises24 = require("fs/promises");
|
|
16753
16901
|
var import_node_os3 = __toESM(require("os"), 1);
|
|
16754
|
-
var
|
|
16902
|
+
var import_node_path32 = __toESM(require("path"), 1);
|
|
16755
16903
|
var EditorInstallError = class extends Error {
|
|
16756
16904
|
constructor(message6, file) {
|
|
16757
16905
|
super(message6);
|
|
@@ -16762,15 +16910,15 @@ var EditorInstallError = class extends Error {
|
|
|
16762
16910
|
};
|
|
16763
16911
|
function editorToolsRoot() {
|
|
16764
16912
|
const override = process.env.PIXELKILN_TOOLS_DIR?.trim();
|
|
16765
|
-
if (override) return
|
|
16766
|
-
if (process.platform === "darwin") return
|
|
16913
|
+
if (override) return import_node_path32.default.resolve(override);
|
|
16914
|
+
if (process.platform === "darwin") return import_node_path32.default.join(import_node_os3.default.homedir(), "Library", "Caches", "pixelkiln", "tools");
|
|
16767
16915
|
if (process.platform === "win32") {
|
|
16768
|
-
return
|
|
16916
|
+
return import_node_path32.default.join(process.env.LOCALAPPDATA ?? import_node_path32.default.join(import_node_os3.default.homedir(), "AppData", "Local"), "pixelkiln", "tools");
|
|
16769
16917
|
}
|
|
16770
|
-
return
|
|
16918
|
+
return import_node_path32.default.join(process.env.XDG_CACHE_HOME ?? import_node_path32.default.join(import_node_os3.default.homedir(), ".cache"), "pixelkiln", "tools");
|
|
16771
16919
|
}
|
|
16772
16920
|
function editorDir(pin = EDITOR_PIN, root = editorToolsRoot()) {
|
|
16773
|
-
return
|
|
16921
|
+
return import_node_path32.default.join(root, "pixelorama", pin.release ?? "unpublished");
|
|
16774
16922
|
}
|
|
16775
16923
|
function editorBaseUrl(pin = EDITOR_PIN) {
|
|
16776
16924
|
const override = process.env.PIXELKILN_EDITOR_URL?.trim();
|
|
@@ -16780,7 +16928,7 @@ function editorBaseUrl(pin = EDITOR_PIN) {
|
|
|
16780
16928
|
}
|
|
16781
16929
|
async function sha256Of(file) {
|
|
16782
16930
|
try {
|
|
16783
|
-
return (0, import_node_crypto7.createHash)("sha256").update(await (0,
|
|
16931
|
+
return (0, import_node_crypto7.createHash)("sha256").update(await (0, import_promises24.readFile)(file)).digest("hex");
|
|
16784
16932
|
} catch {
|
|
16785
16933
|
return null;
|
|
16786
16934
|
}
|
|
@@ -16793,8 +16941,8 @@ async function editorStatus(opts = {}) {
|
|
|
16793
16941
|
let totalBytes = 0;
|
|
16794
16942
|
for (const [name, expected] of Object.entries(pin.files)) {
|
|
16795
16943
|
totalBytes += expected.bytes;
|
|
16796
|
-
const file =
|
|
16797
|
-
const info = (0,
|
|
16944
|
+
const file = import_node_path32.default.join(dir, name);
|
|
16945
|
+
const info = (0, import_node_fs28.existsSync)(file) ? await (0, import_promises24.stat)(file).catch(() => null) : null;
|
|
16798
16946
|
if (info && info.size === expected.bytes && await sha256Of(file) === expected.sha256) {
|
|
16799
16947
|
installedBytes += expected.bytes;
|
|
16800
16948
|
} else {
|
|
@@ -16823,7 +16971,7 @@ async function installEditor(opts = {}) {
|
|
|
16823
16971
|
);
|
|
16824
16972
|
}
|
|
16825
16973
|
if (!baseUrl) throw new EditorInstallError("no download location for the editor build");
|
|
16826
|
-
await (0,
|
|
16974
|
+
await (0, import_promises24.mkdir)(dir, { recursive: true });
|
|
16827
16975
|
const downloaded = [];
|
|
16828
16976
|
const skipped = Object.keys(pin.files).filter((name) => !before.missing.includes(name));
|
|
16829
16977
|
let fetchedBytes = before.installedBytes;
|
|
@@ -16851,13 +16999,13 @@ async function installEditor(opts = {}) {
|
|
|
16851
16999
|
if (digest2 !== expected.sha256) {
|
|
16852
17000
|
throw new EditorInstallError(`${name}: sha256 ${digest2.slice(0, 12)}\u2026 does not match the pinned ${expected.sha256.slice(0, 12)}\u2026`, name);
|
|
16853
17001
|
}
|
|
16854
|
-
const final =
|
|
17002
|
+
const final = import_node_path32.default.join(dir, name);
|
|
16855
17003
|
const tmp = `${final}.${process.pid}.part`;
|
|
16856
17004
|
try {
|
|
16857
|
-
await (0,
|
|
16858
|
-
await (0,
|
|
17005
|
+
await (0, import_promises24.writeFile)(tmp, bytes);
|
|
17006
|
+
await (0, import_promises24.rename)(tmp, final);
|
|
16859
17007
|
} finally {
|
|
16860
|
-
await (0,
|
|
17008
|
+
await (0, import_promises24.rm)(tmp, { force: true });
|
|
16861
17009
|
}
|
|
16862
17010
|
fetchedBytes += bytes.length;
|
|
16863
17011
|
downloaded.push(name);
|
|
@@ -16897,12 +17045,12 @@ var EDITOR_CONTENT_TYPES = {
|
|
|
16897
17045
|
".json": "application/json; charset=utf-8"
|
|
16898
17046
|
};
|
|
16899
17047
|
function editorContentType(name) {
|
|
16900
|
-
return EDITOR_CONTENT_TYPES[
|
|
17048
|
+
return EDITOR_CONTENT_TYPES[import_node_path32.default.extname(name).toLowerCase()] ?? null;
|
|
16901
17049
|
}
|
|
16902
17050
|
|
|
16903
17051
|
// src/gallery/editor.ts
|
|
16904
|
-
var
|
|
16905
|
-
var
|
|
17052
|
+
var import_promises25 = require("fs/promises");
|
|
17053
|
+
var import_node_path33 = __toESM(require("path"), 1);
|
|
16906
17054
|
function editorRoute(release, name = "index.html") {
|
|
16907
17055
|
return `/editor/${encodeURIComponent(release)}/${encodeURIComponent(name)}`;
|
|
16908
17056
|
}
|
|
@@ -16975,7 +17123,7 @@ function createGalleryEditorHandlers(opts = {}) {
|
|
|
16975
17123
|
if (!status.installed) return null;
|
|
16976
17124
|
let bytes;
|
|
16977
17125
|
try {
|
|
16978
|
-
bytes = await (0,
|
|
17126
|
+
bytes = await (0, import_promises25.readFile)(import_node_path33.default.join(dir, name));
|
|
16979
17127
|
} catch {
|
|
16980
17128
|
verified = null;
|
|
16981
17129
|
return null;
|
|
@@ -16992,7 +17140,7 @@ function createGalleryEditorHandlers(opts = {}) {
|
|
|
16992
17140
|
// src/gallery/server.ts
|
|
16993
17141
|
var import_node_crypto8 = require("crypto");
|
|
16994
17142
|
var import_node_http2 = require("http");
|
|
16995
|
-
var
|
|
17143
|
+
var import_promises26 = require("fs/promises");
|
|
16996
17144
|
var MAX_EDIT_BYTES = 48 * 1024 * 1024;
|
|
16997
17145
|
var EDITOR_CSP = [
|
|
16998
17146
|
"default-src 'self'",
|
|
@@ -17020,7 +17168,7 @@ async function readJsonBody(req) {
|
|
|
17020
17168
|
async function serveGallery(opts) {
|
|
17021
17169
|
const log = opts.onProgress ?? (() => {
|
|
17022
17170
|
});
|
|
17023
|
-
const session = opts.edit || opts.generate || opts.editor ? (0, import_node_crypto8.randomBytes)(16).toString("hex") : null;
|
|
17171
|
+
const session = opts.edit || opts.generate || opts.editor || opts.skeleton ? (0, import_node_crypto8.randomBytes)(16).toString("hex") : null;
|
|
17024
17172
|
let media = /* @__PURE__ */ new Map();
|
|
17025
17173
|
let loading = null;
|
|
17026
17174
|
const reviewAssets = /* @__PURE__ */ new Map();
|
|
@@ -17095,6 +17243,17 @@ async function serveGallery(opts) {
|
|
|
17095
17243
|
}
|
|
17096
17244
|
return;
|
|
17097
17245
|
}
|
|
17246
|
+
if (req.method === "POST" && url2.pathname === "/api/skeleton/estimate") {
|
|
17247
|
+
if (!opts.skeleton || !session) return fail(405, "skeleton estimates need the gallery started with --edit");
|
|
17248
|
+
try {
|
|
17249
|
+
const body = await guardedBody("skeleton estimates");
|
|
17250
|
+
if (body === void 0) return;
|
|
17251
|
+
json(200, await opts.skeleton.estimate(body));
|
|
17252
|
+
} catch (err) {
|
|
17253
|
+
reportError("skeleton estimate", err);
|
|
17254
|
+
}
|
|
17255
|
+
return;
|
|
17256
|
+
}
|
|
17098
17257
|
if (req.method === "POST" && url2.pathname === "/api/editor/install") {
|
|
17099
17258
|
if (!opts.editor || !session) return fail(405, "the in-browser editor is off for this gallery");
|
|
17100
17259
|
try {
|
|
@@ -17151,7 +17310,7 @@ async function serveGallery(opts) {
|
|
|
17151
17310
|
const asset = reviewAssets.get(jobId)?.get(url2.pathname);
|
|
17152
17311
|
if (!asset) return fail(404, "no such review asset");
|
|
17153
17312
|
try {
|
|
17154
|
-
const bytes = await (0,
|
|
17313
|
+
const bytes = await (0, import_promises26.readFile)(asset.path);
|
|
17155
17314
|
res.writeHead(200, {
|
|
17156
17315
|
"Content-Type": asset.contentType,
|
|
17157
17316
|
"Cache-Control": "no-store",
|
|
@@ -17165,6 +17324,10 @@ async function serveGallery(opts) {
|
|
|
17165
17324
|
}
|
|
17166
17325
|
}
|
|
17167
17326
|
if (req.method !== "GET" && req.method !== "HEAD") return fail(405, "the gallery is read-only");
|
|
17327
|
+
if (url2.pathname === "/api/skeleton") {
|
|
17328
|
+
if (!opts.skeleton) return fail(404, "skeleton estimates need the gallery started with --edit");
|
|
17329
|
+
return json(200, opts.skeleton.status());
|
|
17330
|
+
}
|
|
17168
17331
|
if (url2.pathname === "/api/jobs") {
|
|
17169
17332
|
if (!opts.generate) return fail(404, "generation is off");
|
|
17170
17333
|
return json(200, opts.generate.status());
|
|
@@ -17198,12 +17361,15 @@ async function serveGallery(opts) {
|
|
|
17198
17361
|
try {
|
|
17199
17362
|
if (url2.pathname === "/") {
|
|
17200
17363
|
const { snapshot } = await load();
|
|
17364
|
+
const nonce = (0, import_node_crypto8.randomBytes)(16).toString("base64");
|
|
17201
17365
|
res.writeHead(200, {
|
|
17202
17366
|
"Content-Type": "text/html; charset=utf-8",
|
|
17203
17367
|
"Cache-Control": "no-store",
|
|
17204
|
-
"X-Content-Type-Options": "nosniff"
|
|
17368
|
+
"X-Content-Type-Options": "nosniff",
|
|
17369
|
+
"Content-Security-Policy": galleryContentSecurityPolicy(nonce)
|
|
17205
17370
|
});
|
|
17206
17371
|
res.end(renderGallery(snapshot, {
|
|
17372
|
+
nonce,
|
|
17207
17373
|
...session ? { session } : {},
|
|
17208
17374
|
editable: Boolean(opts.edit),
|
|
17209
17375
|
generation: Boolean(opts.generate),
|
|
@@ -17227,7 +17393,7 @@ async function serveGallery(opts) {
|
|
|
17227
17393
|
if (!asset) return fail(404, "no such gallery media");
|
|
17228
17394
|
let bytes;
|
|
17229
17395
|
try {
|
|
17230
|
-
bytes = await (0,
|
|
17396
|
+
bytes = await (0, import_promises26.readFile)(asset.path);
|
|
17231
17397
|
} catch {
|
|
17232
17398
|
return fail(404, "gallery media is no longer on disk; refresh the page");
|
|
17233
17399
|
}
|
|
@@ -17271,27 +17437,27 @@ async function serveGallery(opts) {
|
|
|
17271
17437
|
|
|
17272
17438
|
// src/gallery/generate.ts
|
|
17273
17439
|
var import_node_crypto9 = require("crypto");
|
|
17274
|
-
var
|
|
17275
|
-
var GenerateRequestSchema =
|
|
17276
|
-
project:
|
|
17277
|
-
keys:
|
|
17440
|
+
var import_zod12 = require("zod");
|
|
17441
|
+
var GenerateRequestSchema = import_zod12.z.object({
|
|
17442
|
+
project: import_zod12.z.string().min(1).optional(),
|
|
17443
|
+
keys: import_zod12.z.array(import_zod12.z.string().min(1)).min(1).max(500),
|
|
17278
17444
|
/**
|
|
17279
17445
|
* Regenerate work that is up to date, as `gen --force` does, and let the
|
|
17280
17446
|
* fetch replace a locally modified or untracked file. With `restore`, put
|
|
17281
17447
|
* the recorded bytes back over a modified file.
|
|
17282
17448
|
*/
|
|
17283
|
-
force:
|
|
17449
|
+
force: import_zod12.z.boolean().optional(),
|
|
17284
17450
|
/** Advance in-flight, review, or selected work without submitting anything. */
|
|
17285
|
-
resume:
|
|
17451
|
+
resume: import_zod12.z.boolean().optional(),
|
|
17286
17452
|
/** Re-download downloaded outputs and replace files whose object changed upstream. */
|
|
17287
|
-
refresh:
|
|
17453
|
+
refresh: import_zod12.z.boolean().optional(),
|
|
17288
17454
|
/** Put recorded outputs back on disk from the cache or the provider, without generating. */
|
|
17289
|
-
restore:
|
|
17455
|
+
restore: import_zod12.z.boolean().optional(),
|
|
17290
17456
|
/**
|
|
17291
17457
|
* Bring a replaced generation back: 1-based position in the record's
|
|
17292
17458
|
* history (newest first) or an output hash prefix. One key at a time.
|
|
17293
17459
|
*/
|
|
17294
|
-
revert:
|
|
17460
|
+
revert: import_zod12.z.string().min(1).max(64).optional()
|
|
17295
17461
|
}).strict().refine((request) => [request.resume, request.refresh, request.restore, request.revert !== void 0].filter(Boolean).length <= 1, {
|
|
17296
17462
|
message: "resume, refresh, restore, and revert are exclusive"
|
|
17297
17463
|
}).refine((request) => request.revert === void 0 || request.keys.length === 1, { message: "revert takes one key" });
|
|
@@ -17601,19 +17767,19 @@ function createGenerateHandlers(opts) {
|
|
|
17601
17767
|
}
|
|
17602
17768
|
|
|
17603
17769
|
// src/pipeline/salvage.ts
|
|
17604
|
-
var
|
|
17605
|
-
var
|
|
17606
|
-
var
|
|
17770
|
+
var import_promises27 = require("fs/promises");
|
|
17771
|
+
var import_node_fs29 = require("fs");
|
|
17772
|
+
var import_node_path34 = __toESM(require("path"), 1);
|
|
17607
17773
|
function providerClaimId(provider, objectId) {
|
|
17608
17774
|
return `${encodeURIComponent(provider)}:${encodeURIComponent(objectId)}`;
|
|
17609
17775
|
}
|
|
17610
17776
|
async function loadClaims(lockPaths, opts = {}) {
|
|
17611
17777
|
const claimed = /* @__PURE__ */ new Set();
|
|
17612
17778
|
for (const p of lockPaths) {
|
|
17613
|
-
if (!(0,
|
|
17779
|
+
if (!(0, import_node_fs29.existsSync)(p)) throw new Error(`Claim lockfile not found: ${p}`);
|
|
17614
17780
|
let parsed;
|
|
17615
17781
|
try {
|
|
17616
|
-
parsed = parseLock(JSON.parse(await (0,
|
|
17782
|
+
parsed = parseLock(JSON.parse(await (0, import_promises27.readFile)(p, "utf8")));
|
|
17617
17783
|
} catch {
|
|
17618
17784
|
throw new Error(`Claim lockfile is malformed: ${p}`);
|
|
17619
17785
|
}
|
|
@@ -17717,20 +17883,20 @@ function matchOrphanStyle(prompt, manifest) {
|
|
|
17717
17883
|
return matchStyleByPattern(prompt, manifest);
|
|
17718
17884
|
}
|
|
17719
17885
|
async function loadSiblingManifests(ownManifestPath, workspaceManifestPaths, claimPaths) {
|
|
17720
|
-
const own =
|
|
17886
|
+
const own = import_node_path34.default.resolve(ownManifestPath);
|
|
17721
17887
|
const siblingManifestPaths = [
|
|
17722
17888
|
.../* @__PURE__ */ new Set([
|
|
17723
17889
|
...workspaceManifestPaths,
|
|
17724
|
-
...claimPaths.map((c) =>
|
|
17890
|
+
...claimPaths.map((c) => import_node_path34.default.join(import_node_path34.default.dirname(import_node_path34.default.resolve(c)), "pixelkiln.manifest.json"))
|
|
17725
17891
|
])
|
|
17726
17892
|
];
|
|
17727
17893
|
const siblings = [];
|
|
17728
17894
|
for (const siblingManifestPath of siblingManifestPaths) {
|
|
17729
|
-
if (
|
|
17730
|
-
if (!(0,
|
|
17895
|
+
if (import_node_path34.default.resolve(siblingManifestPath) === own) continue;
|
|
17896
|
+
if (!(0, import_node_fs29.existsSync)(siblingManifestPath)) continue;
|
|
17731
17897
|
try {
|
|
17732
17898
|
const { manifest } = await loadManifest(siblingManifestPath);
|
|
17733
|
-
siblings.push({ label:
|
|
17899
|
+
siblings.push({ label: import_node_path34.default.basename(import_node_path34.default.dirname(siblingManifestPath)), manifest });
|
|
17734
17900
|
} catch {
|
|
17735
17901
|
}
|
|
17736
17902
|
}
|
|
@@ -17850,8 +18016,8 @@ async function applyTags(provider, decisions, existing, opts = {}) {
|
|
|
17850
18016
|
}
|
|
17851
18017
|
|
|
17852
18018
|
// src/pick/salvage-server.ts
|
|
17853
|
-
var
|
|
17854
|
-
var
|
|
18019
|
+
var import_promises28 = require("fs/promises");
|
|
18020
|
+
var import_node_path35 = __toESM(require("path"), 1);
|
|
17855
18021
|
|
|
17856
18022
|
// src/pick/salvage-sheet.ts
|
|
17857
18023
|
var escapeHtml3 = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
@@ -18017,7 +18183,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
18017
18183
|
});
|
|
18018
18184
|
const html = renderSalvageSheet(orphans, {
|
|
18019
18185
|
styleId: ctx.styleId,
|
|
18020
|
-
importDir:
|
|
18186
|
+
importDir: import_node_path35.default.relative(process.cwd(), ctx.importDir) || "."
|
|
18021
18187
|
});
|
|
18022
18188
|
const byId = new Map(orphans.map((o) => [o.id, o]));
|
|
18023
18189
|
const existingTags = new Map(orphans.map((o) => [o.id, o.tags]));
|
|
@@ -18073,10 +18239,10 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
18073
18239
|
if (!buf.subarray(0, 8).equals(PNG_SIGNATURE2)) throw new Error("not a PNG");
|
|
18074
18240
|
decodePng(buf);
|
|
18075
18241
|
const assetId = idFromPrompt(orphan.prompt, taken);
|
|
18076
|
-
const rel =
|
|
18077
|
-
const outFile =
|
|
18078
|
-
await (0,
|
|
18079
|
-
await (0,
|
|
18242
|
+
const rel = import_node_path35.default.join("_salvaged", `${assetId}.png`);
|
|
18243
|
+
const outFile = import_node_path35.default.resolve(ctx.importDir, rel);
|
|
18244
|
+
await (0, import_promises28.mkdir)(import_node_path35.default.dirname(outFile), { recursive: true });
|
|
18245
|
+
await (0, import_promises28.writeFile)(outFile, buf);
|
|
18080
18246
|
ctx.manifest.assets[assetId] = {
|
|
18081
18247
|
prompt: orphan.prompt,
|
|
18082
18248
|
promptByStyle: {},
|
|
@@ -18108,7 +18274,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
18108
18274
|
sourceUrl: durableSource,
|
|
18109
18275
|
sourceUrls: durableSource ? [{ url: durableSource }] : [],
|
|
18110
18276
|
outputs: [{
|
|
18111
|
-
path: portableOutputPath(outFile,
|
|
18277
|
+
path: portableOutputPath(outFile, import_node_path35.default.dirname(ctx.manifestPath)),
|
|
18112
18278
|
sha256: sha256(buf)
|
|
18113
18279
|
}],
|
|
18114
18280
|
submittedAt: orphan.createdAt,
|
|
@@ -18132,9 +18298,9 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
18132
18298
|
}
|
|
18133
18299
|
}
|
|
18134
18300
|
await applyTags(provider, decisions, existingTags, { onProgress: log, characters: characterMembers });
|
|
18135
|
-
const raw = JSON.parse(await (0,
|
|
18301
|
+
const raw = JSON.parse(await (0, import_promises28.readFile)(ctx.manifestPath, "utf8"));
|
|
18136
18302
|
for (const id of importedAssetIds) raw.assets[id] = ctx.manifest.assets[id];
|
|
18137
|
-
await (0,
|
|
18303
|
+
await (0, import_promises28.writeFile)(ctx.manifestPath, JSON.stringify(raw, null, 2) + "\n");
|
|
18138
18304
|
await saveLock(ctx.lockPath, ctx.lock);
|
|
18139
18305
|
if (characterAssetIds.length) result.characterAssetIds = characterAssetIds;
|
|
18140
18306
|
return result;
|
|
@@ -18377,6 +18543,7 @@ async function workspaceStatus(ws, dir) {
|
|
|
18377
18543
|
fontOutputPaths,
|
|
18378
18544
|
formatCost,
|
|
18379
18545
|
frameSetFps,
|
|
18546
|
+
galleryContentSecurityPolicy,
|
|
18380
18547
|
galleryMediaId,
|
|
18381
18548
|
galleryMediaRoute,
|
|
18382
18549
|
generateFont,
|