pixelkiln 0.80.0 → 0.82.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 +131 -68
- package/dist/cli.js.map +1 -1
- package/dist/client/gallery.css +55 -1
- package/dist/client/gallery.js +405 -11
- package/dist/index.cjs +105 -42
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +100 -37
- package/dist/index.js.map +1 -1
- package/docs/CHARACTERS.md +7 -0
- package/docs/CLI.md +20 -3
- package/package.json +1 -1
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
|
|
|
@@ -15923,9 +15924,62 @@ var HandEditRequestSchema = z12.discriminatedUnion("action", [
|
|
|
15923
15924
|
protocol: z12.number().int().positive()
|
|
15924
15925
|
}).strict()
|
|
15925
15926
|
]);
|
|
15927
|
+
var UNDO_DEPTH = 20;
|
|
15928
|
+
var UndoRequestSchema = z12.object({
|
|
15929
|
+
action: z12.literal("undo-edit"),
|
|
15930
|
+
project: z12.string().min(1).optional(),
|
|
15931
|
+
/** The manifest as the page last saw it; undo refuses a manifest someone else has changed since. */
|
|
15932
|
+
expectedSha256: z12.string().regex(/^[0-9a-f]{64}$/)
|
|
15933
|
+
}).strict();
|
|
15926
15934
|
function createGalleryEditHandler(opts) {
|
|
15927
15935
|
const log = opts.onProgress ?? (() => {
|
|
15928
15936
|
});
|
|
15937
|
+
const apply = createEditApplier(opts, log);
|
|
15938
|
+
const history = /* @__PURE__ */ new Map();
|
|
15939
|
+
const manifestOf = async (project) => {
|
|
15940
|
+
try {
|
|
15941
|
+
return path30.resolve(await opts.manifestFor(typeof project === "string" ? project : void 0));
|
|
15942
|
+
} catch {
|
|
15943
|
+
return null;
|
|
15944
|
+
}
|
|
15945
|
+
};
|
|
15946
|
+
return async (body) => {
|
|
15947
|
+
const undo = UndoRequestSchema.safeParse(body);
|
|
15948
|
+
if (undo.success) {
|
|
15949
|
+
const manifestPath2 = await manifestOf(undo.data.project);
|
|
15950
|
+
if (!manifestPath2) throw new ManifestEditError("no manifest to undo an edit in");
|
|
15951
|
+
const current = await readFile22(manifestPath2, "utf8");
|
|
15952
|
+
if (sha256(current) !== undo.data.expectedSha256) throw new ManifestDriftError(manifestPath2);
|
|
15953
|
+
const stack = history.get(manifestPath2) ?? [];
|
|
15954
|
+
const last = stack[stack.length - 1];
|
|
15955
|
+
if (!last) throw new ManifestEditError("nothing to undo: this gallery has not changed the manifest");
|
|
15956
|
+
if (last.afterSha !== sha256(current)) {
|
|
15957
|
+
history.delete(manifestPath2);
|
|
15958
|
+
throw Object.assign(new Error("the manifest changed outside the gallery since its last edit; undo would discard that change"), { status: 409 });
|
|
15959
|
+
}
|
|
15960
|
+
const tmp = manifestPath2 + ".pixelkiln-undo";
|
|
15961
|
+
await writeFile14(tmp, last.before);
|
|
15962
|
+
await rename9(tmp, manifestPath2);
|
|
15963
|
+
stack.pop();
|
|
15964
|
+
log(` manifest edit undone in ${path30.relative(process.cwd(), manifestPath2) || manifestPath2}`);
|
|
15965
|
+
return opts.reload();
|
|
15966
|
+
}
|
|
15967
|
+
const manifestPath = await manifestOf(body?.project);
|
|
15968
|
+
const before = manifestPath ? await readFile22(manifestPath, "utf8").catch(() => null) : null;
|
|
15969
|
+
const build = await apply(body);
|
|
15970
|
+
if (manifestPath && before !== null) {
|
|
15971
|
+
const after = await readFile22(manifestPath, "utf8").catch(() => null);
|
|
15972
|
+
if (after !== null && after !== before) {
|
|
15973
|
+
const stack = history.get(manifestPath) ?? [];
|
|
15974
|
+
stack.push({ before, afterSha: sha256(after) });
|
|
15975
|
+
if (stack.length > UNDO_DEPTH) stack.shift();
|
|
15976
|
+
history.set(manifestPath, stack);
|
|
15977
|
+
}
|
|
15978
|
+
}
|
|
15979
|
+
return build;
|
|
15980
|
+
};
|
|
15981
|
+
}
|
|
15982
|
+
function createEditApplier(opts, log) {
|
|
15929
15983
|
return async (body) => {
|
|
15930
15984
|
const handEdit = HandEditRequestSchema.safeParse(body);
|
|
15931
15985
|
if (handEdit.success) {
|
|
@@ -16100,7 +16154,7 @@ function describeHistory(media, entry, cacheDir) {
|
|
|
16100
16154
|
var MAX_COMPARED_BYTES = 8 * 1024 * 1024;
|
|
16101
16155
|
async function samePixels(a, b) {
|
|
16102
16156
|
try {
|
|
16103
|
-
const [left, right] = await Promise.all([
|
|
16157
|
+
const [left, right] = await Promise.all([readFile23(a), readFile23(b)]);
|
|
16104
16158
|
if (left.length > MAX_COMPARED_BYTES || right.length > MAX_COMPARED_BYTES) return false;
|
|
16105
16159
|
const x = decodePng(left);
|
|
16106
16160
|
const y = decodePng(right);
|
|
@@ -16258,7 +16312,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
16258
16312
|
const media = /* @__PURE__ */ new Map();
|
|
16259
16313
|
const plan = await buildPlan(specs, lock);
|
|
16260
16314
|
const items = [];
|
|
16261
|
-
const manifestText = await
|
|
16315
|
+
const manifestText = await readFile23(loaded.path, "utf8");
|
|
16262
16316
|
const manifestSha256 = sha256(manifestText);
|
|
16263
16317
|
const cacheDir = path31.resolve(path31.dirname(opts.lockPath), ".pixelkiln", "cache");
|
|
16264
16318
|
let rawStyles = {};
|
|
@@ -16610,7 +16664,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
16610
16664
|
root: loaded.root,
|
|
16611
16665
|
provider: loaded.manifest.provider,
|
|
16612
16666
|
account: project.account ?? null,
|
|
16613
|
-
manifestSha256: manifestSha256 ?? sha256(await
|
|
16667
|
+
manifestSha256: manifestSha256 ?? sha256(await readFile23(manifestPath, "utf8")),
|
|
16614
16668
|
historyLimit: historyLimit(loaded.manifest),
|
|
16615
16669
|
entries: Object.keys(lock.entries).length,
|
|
16616
16670
|
items: projectItems.length,
|
|
@@ -16738,26 +16792,35 @@ ${css}</style>
|
|
|
16738
16792
|
<span class="project" id="project"></span>
|
|
16739
16793
|
<span class="grow"></span>
|
|
16740
16794
|
<label class="search"><input id="q" type="search" placeholder="Search assets, prompts, ids, hashes" autocomplete="off"><kbd>/</kbd></label>
|
|
16741
|
-
<
|
|
16742
|
-
<
|
|
16743
|
-
<
|
|
16744
|
-
|
|
16745
|
-
|
|
16746
|
-
|
|
16747
|
-
|
|
16748
|
-
|
|
16749
|
-
|
|
16750
|
-
|
|
16751
|
-
|
|
16752
|
-
|
|
16795
|
+
<details class="menu" id="view-menu">
|
|
16796
|
+
<summary title="Sort, group, card size, backdrop, and playback">View</summary>
|
|
16797
|
+
<div class="menu-panel">
|
|
16798
|
+
<label class="menu-row"><span>Sort</span><select id="sort" aria-label="Sort">
|
|
16799
|
+
<option value="key">style / asset</option>
|
|
16800
|
+
<option value="newest">newest first</option>
|
|
16801
|
+
<option value="oldest">oldest first</option>
|
|
16802
|
+
<option value="cost">cost</option>
|
|
16803
|
+
<option value="size">size</option>
|
|
16804
|
+
</select></label>
|
|
16805
|
+
<label class="menu-row"><span>Group</span><select id="group" aria-label="Group">
|
|
16806
|
+
<option value="style">by style</option>
|
|
16807
|
+
<option value="family">by family</option>
|
|
16808
|
+
<option value="none">none</option>
|
|
16809
|
+
</select></label>
|
|
16810
|
+
<div class="menu-row"><span>Cards</span><span class="seg" id="size" role="group" aria-label="Card size">
|
|
16811
|
+
<button type="button" data-size="s" title="Small cards: more on screen">S</button><button type="button" data-size="m" title="Medium cards">M</button><button type="button" data-size="l" title="Large cards: sprites drawn bigger">L</button>
|
|
16812
|
+
</span></div>
|
|
16813
|
+
<div class="menu-row"><span>Backdrop</span><span id="backdrop-slot"></span></div>
|
|
16814
|
+
<label class="menu-row check" title="Play every loop on the grid; without this, a loop plays while the pointer is over it"><input id="playloops" type="checkbox"> Play every loop</label>
|
|
16815
|
+
<label class="menu-row check" title="Refresh every 5 seconds while this tab is visible"><input id="auto" type="checkbox"> Refresh every 5 seconds</label>
|
|
16816
|
+
<label class="menu-row check" id="notify-chip" hidden title="A system notification when a job finishes while this tab is in the background"><input id="notify" type="checkbox"> Notify when a job finishes</label>
|
|
16817
|
+
</div>
|
|
16818
|
+
</details>
|
|
16753
16819
|
<span id="editing" class="editing" hidden title="This gallery can write the manifest. It never contacts a provider.">editing</span>
|
|
16754
16820
|
<button id="select" type="button" title="Pick several records to generate, regenerate, compare, or tag together (or ctrl/\u2318-click a card)">Select</button>
|
|
16755
16821
|
<button id="studio" type="button" hidden title="Draft a new character: its style, base, loops, and portrait, priced as you go">+ New character</button>
|
|
16756
16822
|
<button id="refresh" type="button" title="Re-read the manifest, lockfile, and disk">Refresh</button>
|
|
16757
|
-
<
|
|
16758
|
-
<label class="chip" title="Play every loop on the grid; without this, a loop plays while the pointer is over it"><input id="playloops" type="checkbox"> play loops</label>
|
|
16759
|
-
<span id="backdrop-slot"></span>
|
|
16760
|
-
<label class="chip" id="notify-chip" hidden title="A system notification when a job finishes while this tab is in the background"><input id="notify" type="checkbox"> notify</label>
|
|
16823
|
+
<button id="keys" type="button" title="Keyboard shortcuts (?)" aria-label="Keyboard shortcuts">?</button>
|
|
16761
16824
|
</div>
|
|
16762
16825
|
<div class="totals" id="totals"></div>
|
|
16763
16826
|
<div class="chips" id="chips"></div>
|
|
@@ -16766,9 +16829,9 @@ ${css}</style>
|
|
|
16766
16829
|
</header>
|
|
16767
16830
|
<main id="root"></main>
|
|
16768
16831
|
<div id="selbar" class="selbar" hidden></div>
|
|
16832
|
+
<div id="toast" class="toast" role="status" aria-live="polite" hidden></div>
|
|
16769
16833
|
<footer>
|
|
16770
|
-
Click a sprite for its full record; <kbd>
|
|
16771
|
-
is open, <kbd>,</kbd>/<kbd>.</kbd> step a loop's frames and <kbd>space</kbd> plays it, <kbd>Esc</kbd> closes it, and <kbd>/</kbd> jumps to search. This page reads the manifest, lockfile,
|
|
16834
|
+
Click a sprite for its full record; press <kbd>?</kbd> for every keyboard shortcut. This page reads the manifest, lockfile,
|
|
16772
16835
|
and disk only. It never contacts a provider<span id="foot-edit"> and never writes anything</span><span id="foot-editing" hidden>.
|
|
16773
16836
|
Editing is on: saving rewrites the manifest and nothing else</span><span id="foot-gen" hidden>.
|
|
16774
16837
|
Generation is on under the session budget shown above; every run is the same submit, poll, and fetch as <code>pixelkiln gen</code></span>.
|
|
@@ -16843,7 +16906,7 @@ var EDITOR_RELEASE_BASE = "https://github.com/gfargo/pixelkiln/releases/download
|
|
|
16843
16906
|
// src/editor/install.ts
|
|
16844
16907
|
import { createHash as createHash5 } from "crypto";
|
|
16845
16908
|
import { existsSync as existsSync27 } from "fs";
|
|
16846
|
-
import { mkdir as mkdir11, readFile as
|
|
16909
|
+
import { mkdir as mkdir11, readFile as readFile24, rename as rename10, rm as rm11, stat as stat4, writeFile as writeFile15 } from "fs/promises";
|
|
16847
16910
|
import os2 from "os";
|
|
16848
16911
|
import path32 from "path";
|
|
16849
16912
|
var EditorInstallError = class extends Error {
|
|
@@ -16874,7 +16937,7 @@ function editorBaseUrl(pin = EDITOR_PIN) {
|
|
|
16874
16937
|
}
|
|
16875
16938
|
async function sha256Of(file) {
|
|
16876
16939
|
try {
|
|
16877
|
-
return createHash5("sha256").update(await
|
|
16940
|
+
return createHash5("sha256").update(await readFile24(file)).digest("hex");
|
|
16878
16941
|
} catch {
|
|
16879
16942
|
return null;
|
|
16880
16943
|
}
|
|
@@ -16948,8 +17011,8 @@ async function installEditor(opts = {}) {
|
|
|
16948
17011
|
const final = path32.join(dir, name);
|
|
16949
17012
|
const tmp = `${final}.${process.pid}.part`;
|
|
16950
17013
|
try {
|
|
16951
|
-
await
|
|
16952
|
-
await
|
|
17014
|
+
await writeFile15(tmp, bytes);
|
|
17015
|
+
await rename10(tmp, final);
|
|
16953
17016
|
} finally {
|
|
16954
17017
|
await rm11(tmp, { force: true });
|
|
16955
17018
|
}
|
|
@@ -16995,7 +17058,7 @@ function editorContentType(name) {
|
|
|
16995
17058
|
}
|
|
16996
17059
|
|
|
16997
17060
|
// src/gallery/editor.ts
|
|
16998
|
-
import { readFile as
|
|
17061
|
+
import { readFile as readFile25 } from "fs/promises";
|
|
16999
17062
|
import path33 from "path";
|
|
17000
17063
|
function editorRoute(release, name = "index.html") {
|
|
17001
17064
|
return `/editor/${encodeURIComponent(release)}/${encodeURIComponent(name)}`;
|
|
@@ -17069,7 +17132,7 @@ function createGalleryEditorHandlers(opts = {}) {
|
|
|
17069
17132
|
if (!status.installed) return null;
|
|
17070
17133
|
let bytes;
|
|
17071
17134
|
try {
|
|
17072
|
-
bytes = await
|
|
17135
|
+
bytes = await readFile25(path33.join(dir, name));
|
|
17073
17136
|
} catch {
|
|
17074
17137
|
verified = null;
|
|
17075
17138
|
return null;
|
|
@@ -17086,7 +17149,7 @@ function createGalleryEditorHandlers(opts = {}) {
|
|
|
17086
17149
|
// src/gallery/server.ts
|
|
17087
17150
|
import { randomBytes } from "crypto";
|
|
17088
17151
|
import { createServer as createServer2 } from "http";
|
|
17089
|
-
import { readFile as
|
|
17152
|
+
import { readFile as readFile26 } from "fs/promises";
|
|
17090
17153
|
var MAX_EDIT_BYTES = 48 * 1024 * 1024;
|
|
17091
17154
|
var EDITOR_CSP = [
|
|
17092
17155
|
"default-src 'self'",
|
|
@@ -17267,7 +17330,7 @@ async function serveGallery(opts) {
|
|
|
17267
17330
|
const asset = reviewAssets.get(jobId)?.get(url2.pathname);
|
|
17268
17331
|
if (!asset) return fail(404, "no such review asset");
|
|
17269
17332
|
try {
|
|
17270
|
-
const bytes = await
|
|
17333
|
+
const bytes = await readFile26(asset.path);
|
|
17271
17334
|
res.writeHead(200, {
|
|
17272
17335
|
"Content-Type": asset.contentType,
|
|
17273
17336
|
"Cache-Control": "no-store",
|
|
@@ -17350,7 +17413,7 @@ async function serveGallery(opts) {
|
|
|
17350
17413
|
if (!asset) return fail(404, "no such gallery media");
|
|
17351
17414
|
let bytes;
|
|
17352
17415
|
try {
|
|
17353
|
-
bytes = await
|
|
17416
|
+
bytes = await readFile26(asset.path);
|
|
17354
17417
|
} catch {
|
|
17355
17418
|
return fail(404, "gallery media is no longer on disk; refresh the page");
|
|
17356
17419
|
}
|
|
@@ -17760,7 +17823,7 @@ function createGenerateHandlers(opts) {
|
|
|
17760
17823
|
}
|
|
17761
17824
|
|
|
17762
17825
|
// src/pipeline/salvage.ts
|
|
17763
|
-
import { readFile as
|
|
17826
|
+
import { readFile as readFile27 } from "fs/promises";
|
|
17764
17827
|
import { existsSync as existsSync28 } from "fs";
|
|
17765
17828
|
import path34 from "path";
|
|
17766
17829
|
function providerClaimId(provider, objectId) {
|
|
@@ -17772,7 +17835,7 @@ async function loadClaims(lockPaths, opts = {}) {
|
|
|
17772
17835
|
if (!existsSync28(p)) throw new Error(`Claim lockfile not found: ${p}`);
|
|
17773
17836
|
let parsed;
|
|
17774
17837
|
try {
|
|
17775
|
-
parsed = parseLock(JSON.parse(await
|
|
17838
|
+
parsed = parseLock(JSON.parse(await readFile27(p, "utf8")));
|
|
17776
17839
|
} catch {
|
|
17777
17840
|
throw new Error(`Claim lockfile is malformed: ${p}`);
|
|
17778
17841
|
}
|
|
@@ -18009,7 +18072,7 @@ async function applyTags(provider, decisions, existing, opts = {}) {
|
|
|
18009
18072
|
}
|
|
18010
18073
|
|
|
18011
18074
|
// src/pick/salvage-server.ts
|
|
18012
|
-
import { mkdir as mkdir12, writeFile as
|
|
18075
|
+
import { mkdir as mkdir12, writeFile as writeFile16, readFile as readFile28 } from "fs/promises";
|
|
18013
18076
|
import path35 from "path";
|
|
18014
18077
|
|
|
18015
18078
|
// src/pick/salvage-sheet.ts
|
|
@@ -18235,7 +18298,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
18235
18298
|
const rel = path35.join("_salvaged", `${assetId}.png`);
|
|
18236
18299
|
const outFile = path35.resolve(ctx.importDir, rel);
|
|
18237
18300
|
await mkdir12(path35.dirname(outFile), { recursive: true });
|
|
18238
|
-
await
|
|
18301
|
+
await writeFile16(outFile, buf);
|
|
18239
18302
|
ctx.manifest.assets[assetId] = {
|
|
18240
18303
|
prompt: orphan.prompt,
|
|
18241
18304
|
promptByStyle: {},
|
|
@@ -18291,9 +18354,9 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
18291
18354
|
}
|
|
18292
18355
|
}
|
|
18293
18356
|
await applyTags(provider, decisions, existingTags, { onProgress: log, characters: characterMembers });
|
|
18294
|
-
const raw = JSON.parse(await
|
|
18357
|
+
const raw = JSON.parse(await readFile28(ctx.manifestPath, "utf8"));
|
|
18295
18358
|
for (const id of importedAssetIds) raw.assets[id] = ctx.manifest.assets[id];
|
|
18296
|
-
await
|
|
18359
|
+
await writeFile16(ctx.manifestPath, JSON.stringify(raw, null, 2) + "\n");
|
|
18297
18360
|
await saveLock(ctx.lockPath, ctx.lock);
|
|
18298
18361
|
if (characterAssetIds.length) result.characterAssetIds = characterAssetIds;
|
|
18299
18362
|
return result;
|