pixelkiln 0.81.0 → 0.83.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 +132 -54
- package/dist/cli.js.map +1 -1
- package/dist/client/gallery.css +13 -0
- package/dist/client/gallery.js +375 -14
- package/dist/index.cjs +106 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +516 -8
- package/dist/index.d.ts +516 -8
- package/dist/index.js +101 -23
- package/dist/index.js.map +1 -1
- package/docs/AGENTS.md +4 -2
- package/docs/CHARACTERS.md +11 -3
- package/docs/CLI.md +7 -0
- package/docs/PIXELLAB.md +9 -1
- package/docs/REVISIONS.md +9 -4
- package/package.json +1 -1
- package/skills/pixelkiln/references/pixellab.md +11 -8
- package/skills/pixelkiln/references/revisions.md +13 -10
package/dist/index.js
CHANGED
|
@@ -14931,7 +14931,7 @@ async function runPicker(provider, lock, lockPath, opts = {}) {
|
|
|
14931
14931
|
// src/gallery/snapshot.ts
|
|
14932
14932
|
import { createHash as createHash4 } from "crypto";
|
|
14933
14933
|
import { existsSync as existsSync25 } from "fs";
|
|
14934
|
-
import { readFile as
|
|
14934
|
+
import { readFile as readFile23, stat as stat3 } from "fs/promises";
|
|
14935
14935
|
import path31 from "path";
|
|
14936
14936
|
|
|
14937
14937
|
// src/workspace.ts
|
|
@@ -15068,6 +15068,7 @@ function validateWorkspace(ws, dir) {
|
|
|
15068
15068
|
}
|
|
15069
15069
|
|
|
15070
15070
|
// src/gallery/edit.ts
|
|
15071
|
+
import { readFile as readFile22, rename as rename9, writeFile as writeFile14 } from "fs/promises";
|
|
15071
15072
|
import path30 from "path";
|
|
15072
15073
|
import { z as z12 } from "zod";
|
|
15073
15074
|
|
|
@@ -15124,10 +15125,10 @@ var NewAssetSchema = z8.object({
|
|
|
15124
15125
|
styles: z8.array(z8.string().min(1)).optional(),
|
|
15125
15126
|
/**
|
|
15126
15127
|
* A controlled regeneration of another asset in the same manifest,
|
|
15127
|
-
* instead of a fresh generation. `inpaint` needs a mask upload
|
|
15128
|
-
* gallery does not offer
|
|
15129
|
-
*
|
|
15130
|
-
*
|
|
15128
|
+
* instead of a fresh generation. `inpaint` needs a mask upload and
|
|
15129
|
+
* `interpolate` an ending-keyframe image the gallery does not offer, so
|
|
15130
|
+
* those two are hand-edited; every other mode can be created here
|
|
15131
|
+
* (`animate-skeleton` after the gallery writes its keypoints file).
|
|
15131
15132
|
*/
|
|
15132
15133
|
revision: z8.discriminatedUnion("mode", [
|
|
15133
15134
|
z8.object({
|
|
@@ -15136,6 +15137,29 @@ var NewAssetSchema = z8.object({
|
|
|
15136
15137
|
from: z8.string().min(1),
|
|
15137
15138
|
strength: z8.number().min(0).max(1).optional()
|
|
15138
15139
|
}).strict(),
|
|
15140
|
+
z8.object({
|
|
15141
|
+
mode: z8.literal("reduce-colors"),
|
|
15142
|
+
from: z8.string().min(1),
|
|
15143
|
+
/** A target colour count; the gallery leaves PixelLab's default when this is omitted. */
|
|
15144
|
+
numColors: z8.number().int().min(2).max(256).optional(),
|
|
15145
|
+
dithering: RevisionDitheringSchema.optional(),
|
|
15146
|
+
ditheringStrength: z8.number().min(0).max(10).optional()
|
|
15147
|
+
}).strict(),
|
|
15148
|
+
z8.object({ mode: z8.literal("correct-pixelart"), from: z8.string().min(1), strength: z8.number().min(0).max(1).optional() }).strict(),
|
|
15149
|
+
z8.object({
|
|
15150
|
+
mode: z8.enum(["animate", "animate-pixminimax"]),
|
|
15151
|
+
from: z8.string().min(1),
|
|
15152
|
+
frames: z8.number().int().min(4).max(40).refine((n) => n % 2 === 0, "frames must be even").optional(),
|
|
15153
|
+
fps: z8.number().int().min(1).max(60).optional(),
|
|
15154
|
+
enhancePrompt: z8.boolean().optional(),
|
|
15155
|
+
/** `animate-pixminimax` only; the loader refuses it on `animate`. */
|
|
15156
|
+
direction: CharacterDirectionSchema.optional()
|
|
15157
|
+
}).strict(),
|
|
15158
|
+
z8.object({
|
|
15159
|
+
mode: z8.literal("edit-animation"),
|
|
15160
|
+
from: z8.string().min(1),
|
|
15161
|
+
fps: z8.number().int().min(1).max(60).optional()
|
|
15162
|
+
}).strict(),
|
|
15139
15163
|
z8.object({
|
|
15140
15164
|
mode: z8.literal("animate-skeleton"),
|
|
15141
15165
|
from: z8.string().min(1),
|
|
@@ -15923,9 +15947,62 @@ var HandEditRequestSchema = z12.discriminatedUnion("action", [
|
|
|
15923
15947
|
protocol: z12.number().int().positive()
|
|
15924
15948
|
}).strict()
|
|
15925
15949
|
]);
|
|
15950
|
+
var UNDO_DEPTH = 20;
|
|
15951
|
+
var UndoRequestSchema = z12.object({
|
|
15952
|
+
action: z12.literal("undo-edit"),
|
|
15953
|
+
project: z12.string().min(1).optional(),
|
|
15954
|
+
/** The manifest as the page last saw it; undo refuses a manifest someone else has changed since. */
|
|
15955
|
+
expectedSha256: z12.string().regex(/^[0-9a-f]{64}$/)
|
|
15956
|
+
}).strict();
|
|
15926
15957
|
function createGalleryEditHandler(opts) {
|
|
15927
15958
|
const log = opts.onProgress ?? (() => {
|
|
15928
15959
|
});
|
|
15960
|
+
const apply = createEditApplier(opts, log);
|
|
15961
|
+
const history = /* @__PURE__ */ new Map();
|
|
15962
|
+
const manifestOf = async (project) => {
|
|
15963
|
+
try {
|
|
15964
|
+
return path30.resolve(await opts.manifestFor(typeof project === "string" ? project : void 0));
|
|
15965
|
+
} catch {
|
|
15966
|
+
return null;
|
|
15967
|
+
}
|
|
15968
|
+
};
|
|
15969
|
+
return async (body) => {
|
|
15970
|
+
const undo = UndoRequestSchema.safeParse(body);
|
|
15971
|
+
if (undo.success) {
|
|
15972
|
+
const manifestPath2 = await manifestOf(undo.data.project);
|
|
15973
|
+
if (!manifestPath2) throw new ManifestEditError("no manifest to undo an edit in");
|
|
15974
|
+
const current = await readFile22(manifestPath2, "utf8");
|
|
15975
|
+
if (sha256(current) !== undo.data.expectedSha256) throw new ManifestDriftError(manifestPath2);
|
|
15976
|
+
const stack = history.get(manifestPath2) ?? [];
|
|
15977
|
+
const last = stack[stack.length - 1];
|
|
15978
|
+
if (!last) throw new ManifestEditError("nothing to undo: this gallery has not changed the manifest");
|
|
15979
|
+
if (last.afterSha !== sha256(current)) {
|
|
15980
|
+
history.delete(manifestPath2);
|
|
15981
|
+
throw Object.assign(new Error("the manifest changed outside the gallery since its last edit; undo would discard that change"), { status: 409 });
|
|
15982
|
+
}
|
|
15983
|
+
const tmp = manifestPath2 + ".pixelkiln-undo";
|
|
15984
|
+
await writeFile14(tmp, last.before);
|
|
15985
|
+
await rename9(tmp, manifestPath2);
|
|
15986
|
+
stack.pop();
|
|
15987
|
+
log(` manifest edit undone in ${path30.relative(process.cwd(), manifestPath2) || manifestPath2}`);
|
|
15988
|
+
return opts.reload();
|
|
15989
|
+
}
|
|
15990
|
+
const manifestPath = await manifestOf(body?.project);
|
|
15991
|
+
const before = manifestPath ? await readFile22(manifestPath, "utf8").catch(() => null) : null;
|
|
15992
|
+
const build = await apply(body);
|
|
15993
|
+
if (manifestPath && before !== null) {
|
|
15994
|
+
const after = await readFile22(manifestPath, "utf8").catch(() => null);
|
|
15995
|
+
if (after !== null && after !== before) {
|
|
15996
|
+
const stack = history.get(manifestPath) ?? [];
|
|
15997
|
+
stack.push({ before, afterSha: sha256(after) });
|
|
15998
|
+
if (stack.length > UNDO_DEPTH) stack.shift();
|
|
15999
|
+
history.set(manifestPath, stack);
|
|
16000
|
+
}
|
|
16001
|
+
}
|
|
16002
|
+
return build;
|
|
16003
|
+
};
|
|
16004
|
+
}
|
|
16005
|
+
function createEditApplier(opts, log) {
|
|
15929
16006
|
return async (body) => {
|
|
15930
16007
|
const handEdit = HandEditRequestSchema.safeParse(body);
|
|
15931
16008
|
if (handEdit.success) {
|
|
@@ -16100,7 +16177,7 @@ function describeHistory(media, entry, cacheDir) {
|
|
|
16100
16177
|
var MAX_COMPARED_BYTES = 8 * 1024 * 1024;
|
|
16101
16178
|
async function samePixels(a, b) {
|
|
16102
16179
|
try {
|
|
16103
|
-
const [left, right] = await Promise.all([
|
|
16180
|
+
const [left, right] = await Promise.all([readFile23(a), readFile23(b)]);
|
|
16104
16181
|
if (left.length > MAX_COMPARED_BYTES || right.length > MAX_COMPARED_BYTES) return false;
|
|
16105
16182
|
const x = decodePng(left);
|
|
16106
16183
|
const y = decodePng(right);
|
|
@@ -16258,7 +16335,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
16258
16335
|
const media = /* @__PURE__ */ new Map();
|
|
16259
16336
|
const plan = await buildPlan(specs, lock);
|
|
16260
16337
|
const items = [];
|
|
16261
|
-
const manifestText = await
|
|
16338
|
+
const manifestText = await readFile23(loaded.path, "utf8");
|
|
16262
16339
|
const manifestSha256 = sha256(manifestText);
|
|
16263
16340
|
const cacheDir = path31.resolve(path31.dirname(opts.lockPath), ".pixelkiln", "cache");
|
|
16264
16341
|
let rawStyles = {};
|
|
@@ -16610,7 +16687,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
16610
16687
|
root: loaded.root,
|
|
16611
16688
|
provider: loaded.manifest.provider,
|
|
16612
16689
|
account: project.account ?? null,
|
|
16613
|
-
manifestSha256: manifestSha256 ?? sha256(await
|
|
16690
|
+
manifestSha256: manifestSha256 ?? sha256(await readFile23(manifestPath, "utf8")),
|
|
16614
16691
|
historyLimit: historyLimit(loaded.manifest),
|
|
16615
16692
|
entries: Object.keys(lock.entries).length,
|
|
16616
16693
|
items: projectItems.length,
|
|
@@ -16775,6 +16852,7 @@ ${css}</style>
|
|
|
16775
16852
|
</header>
|
|
16776
16853
|
<main id="root"></main>
|
|
16777
16854
|
<div id="selbar" class="selbar" hidden></div>
|
|
16855
|
+
<div id="toast" class="toast" role="status" aria-live="polite" hidden></div>
|
|
16778
16856
|
<footer>
|
|
16779
16857
|
Click a sprite for its full record; press <kbd>?</kbd> for every keyboard shortcut. This page reads the manifest, lockfile,
|
|
16780
16858
|
and disk only. It never contacts a provider<span id="foot-edit"> and never writes anything</span><span id="foot-editing" hidden>.
|
|
@@ -16851,7 +16929,7 @@ var EDITOR_RELEASE_BASE = "https://github.com/gfargo/pixelkiln/releases/download
|
|
|
16851
16929
|
// src/editor/install.ts
|
|
16852
16930
|
import { createHash as createHash5 } from "crypto";
|
|
16853
16931
|
import { existsSync as existsSync27 } from "fs";
|
|
16854
|
-
import { mkdir as mkdir11, readFile as
|
|
16932
|
+
import { mkdir as mkdir11, readFile as readFile24, rename as rename10, rm as rm11, stat as stat4, writeFile as writeFile15 } from "fs/promises";
|
|
16855
16933
|
import os2 from "os";
|
|
16856
16934
|
import path32 from "path";
|
|
16857
16935
|
var EditorInstallError = class extends Error {
|
|
@@ -16882,7 +16960,7 @@ function editorBaseUrl(pin = EDITOR_PIN) {
|
|
|
16882
16960
|
}
|
|
16883
16961
|
async function sha256Of(file) {
|
|
16884
16962
|
try {
|
|
16885
|
-
return createHash5("sha256").update(await
|
|
16963
|
+
return createHash5("sha256").update(await readFile24(file)).digest("hex");
|
|
16886
16964
|
} catch {
|
|
16887
16965
|
return null;
|
|
16888
16966
|
}
|
|
@@ -16956,8 +17034,8 @@ async function installEditor(opts = {}) {
|
|
|
16956
17034
|
const final = path32.join(dir, name);
|
|
16957
17035
|
const tmp = `${final}.${process.pid}.part`;
|
|
16958
17036
|
try {
|
|
16959
|
-
await
|
|
16960
|
-
await
|
|
17037
|
+
await writeFile15(tmp, bytes);
|
|
17038
|
+
await rename10(tmp, final);
|
|
16961
17039
|
} finally {
|
|
16962
17040
|
await rm11(tmp, { force: true });
|
|
16963
17041
|
}
|
|
@@ -17003,7 +17081,7 @@ function editorContentType(name) {
|
|
|
17003
17081
|
}
|
|
17004
17082
|
|
|
17005
17083
|
// src/gallery/editor.ts
|
|
17006
|
-
import { readFile as
|
|
17084
|
+
import { readFile as readFile25 } from "fs/promises";
|
|
17007
17085
|
import path33 from "path";
|
|
17008
17086
|
function editorRoute(release, name = "index.html") {
|
|
17009
17087
|
return `/editor/${encodeURIComponent(release)}/${encodeURIComponent(name)}`;
|
|
@@ -17077,7 +17155,7 @@ function createGalleryEditorHandlers(opts = {}) {
|
|
|
17077
17155
|
if (!status.installed) return null;
|
|
17078
17156
|
let bytes;
|
|
17079
17157
|
try {
|
|
17080
|
-
bytes = await
|
|
17158
|
+
bytes = await readFile25(path33.join(dir, name));
|
|
17081
17159
|
} catch {
|
|
17082
17160
|
verified = null;
|
|
17083
17161
|
return null;
|
|
@@ -17094,7 +17172,7 @@ function createGalleryEditorHandlers(opts = {}) {
|
|
|
17094
17172
|
// src/gallery/server.ts
|
|
17095
17173
|
import { randomBytes } from "crypto";
|
|
17096
17174
|
import { createServer as createServer2 } from "http";
|
|
17097
|
-
import { readFile as
|
|
17175
|
+
import { readFile as readFile26 } from "fs/promises";
|
|
17098
17176
|
var MAX_EDIT_BYTES = 48 * 1024 * 1024;
|
|
17099
17177
|
var EDITOR_CSP = [
|
|
17100
17178
|
"default-src 'self'",
|
|
@@ -17275,7 +17353,7 @@ async function serveGallery(opts) {
|
|
|
17275
17353
|
const asset = reviewAssets.get(jobId)?.get(url2.pathname);
|
|
17276
17354
|
if (!asset) return fail(404, "no such review asset");
|
|
17277
17355
|
try {
|
|
17278
|
-
const bytes = await
|
|
17356
|
+
const bytes = await readFile26(asset.path);
|
|
17279
17357
|
res.writeHead(200, {
|
|
17280
17358
|
"Content-Type": asset.contentType,
|
|
17281
17359
|
"Cache-Control": "no-store",
|
|
@@ -17358,7 +17436,7 @@ async function serveGallery(opts) {
|
|
|
17358
17436
|
if (!asset) return fail(404, "no such gallery media");
|
|
17359
17437
|
let bytes;
|
|
17360
17438
|
try {
|
|
17361
|
-
bytes = await
|
|
17439
|
+
bytes = await readFile26(asset.path);
|
|
17362
17440
|
} catch {
|
|
17363
17441
|
return fail(404, "gallery media is no longer on disk; refresh the page");
|
|
17364
17442
|
}
|
|
@@ -17768,7 +17846,7 @@ function createGenerateHandlers(opts) {
|
|
|
17768
17846
|
}
|
|
17769
17847
|
|
|
17770
17848
|
// src/pipeline/salvage.ts
|
|
17771
|
-
import { readFile as
|
|
17849
|
+
import { readFile as readFile27 } from "fs/promises";
|
|
17772
17850
|
import { existsSync as existsSync28 } from "fs";
|
|
17773
17851
|
import path34 from "path";
|
|
17774
17852
|
function providerClaimId(provider, objectId) {
|
|
@@ -17780,7 +17858,7 @@ async function loadClaims(lockPaths, opts = {}) {
|
|
|
17780
17858
|
if (!existsSync28(p)) throw new Error(`Claim lockfile not found: ${p}`);
|
|
17781
17859
|
let parsed;
|
|
17782
17860
|
try {
|
|
17783
|
-
parsed = parseLock(JSON.parse(await
|
|
17861
|
+
parsed = parseLock(JSON.parse(await readFile27(p, "utf8")));
|
|
17784
17862
|
} catch {
|
|
17785
17863
|
throw new Error(`Claim lockfile is malformed: ${p}`);
|
|
17786
17864
|
}
|
|
@@ -18017,7 +18095,7 @@ async function applyTags(provider, decisions, existing, opts = {}) {
|
|
|
18017
18095
|
}
|
|
18018
18096
|
|
|
18019
18097
|
// src/pick/salvage-server.ts
|
|
18020
|
-
import { mkdir as mkdir12, writeFile as
|
|
18098
|
+
import { mkdir as mkdir12, writeFile as writeFile16, readFile as readFile28 } from "fs/promises";
|
|
18021
18099
|
import path35 from "path";
|
|
18022
18100
|
|
|
18023
18101
|
// src/pick/salvage-sheet.ts
|
|
@@ -18243,7 +18321,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
18243
18321
|
const rel = path35.join("_salvaged", `${assetId}.png`);
|
|
18244
18322
|
const outFile = path35.resolve(ctx.importDir, rel);
|
|
18245
18323
|
await mkdir12(path35.dirname(outFile), { recursive: true });
|
|
18246
|
-
await
|
|
18324
|
+
await writeFile16(outFile, buf);
|
|
18247
18325
|
ctx.manifest.assets[assetId] = {
|
|
18248
18326
|
prompt: orphan.prompt,
|
|
18249
18327
|
promptByStyle: {},
|
|
@@ -18299,9 +18377,9 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
18299
18377
|
}
|
|
18300
18378
|
}
|
|
18301
18379
|
await applyTags(provider, decisions, existingTags, { onProgress: log, characters: characterMembers });
|
|
18302
|
-
const raw = JSON.parse(await
|
|
18380
|
+
const raw = JSON.parse(await readFile28(ctx.manifestPath, "utf8"));
|
|
18303
18381
|
for (const id of importedAssetIds) raw.assets[id] = ctx.manifest.assets[id];
|
|
18304
|
-
await
|
|
18382
|
+
await writeFile16(ctx.manifestPath, JSON.stringify(raw, null, 2) + "\n");
|
|
18305
18383
|
await saveLock(ctx.lockPath, ctx.lock);
|
|
18306
18384
|
if (characterAssetIds.length) result.characterAssetIds = characterAssetIds;
|
|
18307
18385
|
return result;
|