pixelkiln 0.63.0 → 0.64.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/README.md +2 -2
- package/dist/cli.js +252 -36
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +110 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +35 -2
- package/dist/index.d.ts +35 -2
- package/dist/index.js +110 -17
- package/dist/index.js.map +1 -1
- package/docs/CLI.md +6 -3
- package/docs/RECOVERY.md +36 -3
- package/package.json +1 -1
- package/skills/pixelkiln/SKILL.md +2 -1
package/dist/index.cjs
CHANGED
|
@@ -5052,8 +5052,8 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5052
5052
|
const sources = [];
|
|
5053
5053
|
const terrainTiles = [];
|
|
5054
5054
|
for (const [index, tile] of tileset.tiles.entries()) {
|
|
5055
|
-
const
|
|
5056
|
-
const role = `tile-${String(index).padStart(2, "0")}-${
|
|
5055
|
+
const slug2 = tile.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "tile";
|
|
5056
|
+
const role = `tile-${String(index).padStart(2, "0")}-${slug2}`;
|
|
5057
5057
|
const file = import_node_path.default.join(_PixelLabProvider.cacheDir(), `${tilesetId}-${index}.png`);
|
|
5058
5058
|
(0, import_node_fs2.writeFileSync)(file, Buffer.from(tile.image.base64, "base64"));
|
|
5059
5059
|
sources.push({ url: `file://${file}`, role });
|
|
@@ -5172,6 +5172,9 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5172
5172
|
async delete(assetId) {
|
|
5173
5173
|
await this.client.deleteObject(assetId);
|
|
5174
5174
|
}
|
|
5175
|
+
async deleteCharacter(id) {
|
|
5176
|
+
await this.client.deleteCharacter(id);
|
|
5177
|
+
}
|
|
5175
5178
|
async *listCharacters() {
|
|
5176
5179
|
for await (const character of this.client.iterateCharacters(100)) {
|
|
5177
5180
|
yield remoteCharacter(character);
|
|
@@ -7195,6 +7198,8 @@ var FakeProvider = class {
|
|
|
7195
7198
|
this.tags.set(objectId, [...tags]);
|
|
7196
7199
|
const asset = this.assets.get(objectId);
|
|
7197
7200
|
if (asset) asset.tags = [...tags];
|
|
7201
|
+
const character = this.characters.get(objectId.split("#")[0]);
|
|
7202
|
+
if (character) character.tags = [...tags];
|
|
7198
7203
|
}
|
|
7199
7204
|
/**
|
|
7200
7205
|
* Assigned in the constructor rather than declared as a method, so a test can
|
|
@@ -7229,6 +7234,10 @@ var FakeProvider = class {
|
|
|
7229
7234
|
this.deleted.push(assetId);
|
|
7230
7235
|
this.assets.delete(assetId);
|
|
7231
7236
|
}
|
|
7237
|
+
async deleteCharacter(id) {
|
|
7238
|
+
this.deleted.push(id);
|
|
7239
|
+
this.characters.delete(id);
|
|
7240
|
+
}
|
|
7232
7241
|
/** Seed a pre-existing remote asset, for adopt / salvage tests. */
|
|
7233
7242
|
seed(asset) {
|
|
7234
7243
|
const full = {
|
|
@@ -16753,6 +16762,9 @@ async function loadClaims(lockPaths, opts = {}) {
|
|
|
16753
16762
|
add(entry.objectId);
|
|
16754
16763
|
add(entry.reviewObjectId);
|
|
16755
16764
|
add(entry.jobId);
|
|
16765
|
+
for (const id of [entry.objectId, entry.jobId]) {
|
|
16766
|
+
if (id?.includes("#")) add(id.split("#")[0]);
|
|
16767
|
+
}
|
|
16756
16768
|
}
|
|
16757
16769
|
}
|
|
16758
16770
|
return claimed;
|
|
@@ -16776,6 +16788,54 @@ async function findOrphans(provider, claimed, opts = {}) {
|
|
|
16776
16788
|
})).sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
16777
16789
|
return { orphans, total: all.length };
|
|
16778
16790
|
}
|
|
16791
|
+
function isState(character) {
|
|
16792
|
+
return Boolean(character.stateName && character.stateName !== "Idle");
|
|
16793
|
+
}
|
|
16794
|
+
function characterSalvageAssets(characters, ctx) {
|
|
16795
|
+
const ordered = [...characters].sort((a, b) => Number(isState(a)) - Number(isState(b)) || a.createdAt.localeCompare(b.createdAt));
|
|
16796
|
+
const base = ordered[0];
|
|
16797
|
+
const common = { styles: [ctx.styleId], tags: ["salvaged"] };
|
|
16798
|
+
const assets = {};
|
|
16799
|
+
const skipped = [];
|
|
16800
|
+
const baseId = idFromPrompt(base.name || base.prompt, ctx.taken);
|
|
16801
|
+
const size = base.width === base.height && base.width >= 32 && base.width <= 256 && base.width !== ctx.styleSize ? { size: base.width } : {};
|
|
16802
|
+
assets[baseId] = { prompt: base.prompt || base.name || baseId, remoteId: base.id, ...size, ...common };
|
|
16803
|
+
const assetIdOf = /* @__PURE__ */ new Map([[base.id, baseId]]);
|
|
16804
|
+
for (const state of ordered.slice(1)) {
|
|
16805
|
+
const stateId = uniqueId(`${baseId}_${slug(state.stateName ?? "state")}`, ctx.taken);
|
|
16806
|
+
assetIdOf.set(state.id, stateId);
|
|
16807
|
+
assets[stateId] = { prompt: state.prompt || state.stateName || stateId, state: { of: baseId }, remoteId: state.id, ...common };
|
|
16808
|
+
}
|
|
16809
|
+
for (const character of ordered) {
|
|
16810
|
+
const parentId = assetIdOf.get(character.id);
|
|
16811
|
+
for (const animation of character.animations) {
|
|
16812
|
+
if (!animation.frames.length) continue;
|
|
16813
|
+
const label = animation.name || animation.type;
|
|
16814
|
+
if (!animation.groupId) {
|
|
16815
|
+
skipped.push(`${parentId}: "${label}" ${animation.direction} loop has no animation group id to adopt it by`);
|
|
16816
|
+
continue;
|
|
16817
|
+
}
|
|
16818
|
+
const loopId = uniqueId(`${parentId}.${slug(label)}.${animation.direction}`, ctx.taken);
|
|
16819
|
+
assets[loopId] = {
|
|
16820
|
+
prompt: label,
|
|
16821
|
+
animation: { of: parentId, direction: animation.direction },
|
|
16822
|
+
remoteId: `${character.id}#${animation.groupId}`,
|
|
16823
|
+
...common
|
|
16824
|
+
};
|
|
16825
|
+
}
|
|
16826
|
+
}
|
|
16827
|
+
return { assets, skipped };
|
|
16828
|
+
}
|
|
16829
|
+
function slug(text) {
|
|
16830
|
+
return text.toLowerCase().replace(/^pixelkiln:.*\//, "").replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "loop";
|
|
16831
|
+
}
|
|
16832
|
+
function uniqueId(base, taken) {
|
|
16833
|
+
let id = base;
|
|
16834
|
+
let n = 2;
|
|
16835
|
+
while (taken.has(id)) id = `${base}_${n++}`;
|
|
16836
|
+
taken.add(id);
|
|
16837
|
+
return id;
|
|
16838
|
+
}
|
|
16779
16839
|
var MIN_STYLE_FRAGMENT_LENGTH = 12;
|
|
16780
16840
|
function matchStyleByPattern(prompt, manifest) {
|
|
16781
16841
|
const p = prompt.toLowerCase();
|
|
@@ -16905,18 +16965,22 @@ async function applyTags(provider, decisions, existing, opts = {}) {
|
|
|
16905
16965
|
let tagged = 0;
|
|
16906
16966
|
let failed = 0;
|
|
16907
16967
|
for (const { id, action } of decisions) {
|
|
16908
|
-
const
|
|
16909
|
-
|
|
16910
|
-
)
|
|
16911
|
-
|
|
16912
|
-
|
|
16913
|
-
|
|
16914
|
-
|
|
16915
|
-
|
|
16916
|
-
tagged
|
|
16917
|
-
|
|
16918
|
-
|
|
16919
|
-
|
|
16968
|
+
const group = opts.characters?.get(id);
|
|
16969
|
+
const targets = group ?? [{ id, tags: existing.get(id) ?? [] }];
|
|
16970
|
+
for (const target of targets) {
|
|
16971
|
+
const current = target.tags.filter(
|
|
16972
|
+
(t) => t !== "pixelkiln:keep" && t !== "pixelkiln:discard" && t !== "pixelkiln:imported"
|
|
16973
|
+
);
|
|
16974
|
+
const decisionTag = `pixelkiln:${action === "import" ? "imported" : action}`;
|
|
16975
|
+
const next = [...current.slice(0, 19), decisionTag];
|
|
16976
|
+
if (!provider.setTags) return { tagged, failed };
|
|
16977
|
+
try {
|
|
16978
|
+
await provider.setTags(target.id, next, group ? "character" : void 0);
|
|
16979
|
+
tagged++;
|
|
16980
|
+
} catch (err) {
|
|
16981
|
+
failed++;
|
|
16982
|
+
log(` tag failed ${target.id}: ${err instanceof Error ? err.message : String(err)}`);
|
|
16983
|
+
}
|
|
16920
16984
|
}
|
|
16921
16985
|
}
|
|
16922
16986
|
return { tagged, failed };
|
|
@@ -16929,7 +16993,7 @@ var import_node_path34 = __toESM(require("path"), 1);
|
|
|
16929
16993
|
// src/pick/salvage-sheet.ts
|
|
16930
16994
|
var escapeHtml3 = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
16931
16995
|
function renderSalvageSheet(orphans, ctx) {
|
|
16932
|
-
const safe = orphans.map((o) => ({ ...o, prompt: escapeHtml3(o.prompt) }));
|
|
16996
|
+
const safe = orphans.map((o) => ({ ...o, prompt: escapeHtml3(o.prompt), ...o.note ? { note: escapeHtml3(o.note) } : {} }));
|
|
16933
16997
|
const data = JSON.stringify(safe).replace(/<\//g, "<\\/");
|
|
16934
16998
|
const styleId = ctx ? escapeHtml3(ctx.styleId) : null;
|
|
16935
16999
|
const importDir = ctx ? escapeHtml3(ctx.importDir) : null;
|
|
@@ -17015,6 +17079,7 @@ ITEMS.forEach((o, i) => {
|
|
|
17015
17079
|
el.innerHTML =
|
|
17016
17080
|
'<div class="thumb"><img src="' + o.previewUrl + '" loading="lazy"></div>' +
|
|
17017
17081
|
'<div class="meta">' + o.width + '\xD7' + o.height + ' \xB7 ' + o.createdAt.slice(0,10) + '</div>' +
|
|
17082
|
+
(o.note ? '<div class="meta">' + o.note + '</div>' : '') +
|
|
17018
17083
|
'<div class="prompt">' + o.prompt + '</div>' +
|
|
17019
17084
|
'<div class="acts">' +
|
|
17020
17085
|
'<button data-a="import">import</button>' +
|
|
@@ -17093,6 +17158,9 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
17093
17158
|
});
|
|
17094
17159
|
const byId = new Map(orphans.map((o) => [o.id, o]));
|
|
17095
17160
|
const existingTags = new Map(orphans.map((o) => [o.id, o.tags]));
|
|
17161
|
+
const characterMembers = new Map(
|
|
17162
|
+
orphans.flatMap((o) => o.kind === "character" && o.members ? [[o.id, o.members]] : [])
|
|
17163
|
+
);
|
|
17096
17164
|
return serveReviewPage({
|
|
17097
17165
|
html,
|
|
17098
17166
|
port: opts.port,
|
|
@@ -17109,10 +17177,34 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
17109
17177
|
const result = { imported: 0, kept: 0, discarded: 0, failed: 0 };
|
|
17110
17178
|
const taken = new Set(Object.keys(ctx.manifest.assets));
|
|
17111
17179
|
const importedAssetIds = [];
|
|
17180
|
+
const characterAssetIds = [];
|
|
17112
17181
|
for (const decision of decisions) {
|
|
17113
17182
|
const orphan = byId.get(decision.id);
|
|
17114
17183
|
if (!orphan) continue;
|
|
17115
|
-
if (decision.action === "import") {
|
|
17184
|
+
if (decision.action === "import" && orphan.kind === "character") {
|
|
17185
|
+
try {
|
|
17186
|
+
if (!provider.getCharacter) throw new Error(`provider ${provider.id} cannot read characters`);
|
|
17187
|
+
const details = [];
|
|
17188
|
+
for (const member of orphan.members ?? [{ id: orphan.id }]) details.push(await provider.getCharacter(member.id));
|
|
17189
|
+
const style = ctx.manifest.styles[ctx.styleId];
|
|
17190
|
+
const { assets, skipped } = characterSalvageAssets(details, {
|
|
17191
|
+
styleId: ctx.styleId,
|
|
17192
|
+
styleSize: style?.size,
|
|
17193
|
+
taken
|
|
17194
|
+
});
|
|
17195
|
+
for (const [assetId, asset] of Object.entries(assets)) {
|
|
17196
|
+
ctx.manifest.assets[assetId] = asset;
|
|
17197
|
+
importedAssetIds.push(assetId);
|
|
17198
|
+
characterAssetIds.push(assetId);
|
|
17199
|
+
}
|
|
17200
|
+
for (const line of skipped) log(` not imported: ${line}`);
|
|
17201
|
+
result.imported++;
|
|
17202
|
+
log(` imported character ${orphan.id} as ${Object.keys(assets).length} asset(s)`);
|
|
17203
|
+
} catch (err) {
|
|
17204
|
+
result.failed++;
|
|
17205
|
+
log(` import failed ${orphan.id}: ${err instanceof Error ? err.message : String(err)}`);
|
|
17206
|
+
}
|
|
17207
|
+
} else if (decision.action === "import") {
|
|
17116
17208
|
try {
|
|
17117
17209
|
const buf = await provider.download(orphan.previewUrl);
|
|
17118
17210
|
if (!buf.subarray(0, 8).equals(PNG_SIGNATURE2)) throw new Error("not a PNG");
|
|
@@ -17176,11 +17268,12 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
17176
17268
|
result.discarded++;
|
|
17177
17269
|
}
|
|
17178
17270
|
}
|
|
17179
|
-
await applyTags(provider, decisions, existingTags, { onProgress: log });
|
|
17271
|
+
await applyTags(provider, decisions, existingTags, { onProgress: log, characters: characterMembers });
|
|
17180
17272
|
const raw = JSON.parse(await (0, import_promises27.readFile)(ctx.manifestPath, "utf8"));
|
|
17181
17273
|
for (const id of importedAssetIds) raw.assets[id] = ctx.manifest.assets[id];
|
|
17182
17274
|
await (0, import_promises27.writeFile)(ctx.manifestPath, JSON.stringify(raw, null, 2) + "\n");
|
|
17183
17275
|
await saveLock(ctx.lockPath, ctx.lock);
|
|
17276
|
+
if (characterAssetIds.length) result.characterAssetIds = characterAssetIds;
|
|
17184
17277
|
return result;
|
|
17185
17278
|
}
|
|
17186
17279
|
});
|