pixelkiln 0.62.0 → 0.63.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 -12
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +130 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +115 -46
- package/dist/index.d.ts +115 -46
- package/dist/index.js +130 -12
- package/dist/index.js.map +1 -1
- package/docs/CHARACTERS.md +39 -0
- package/docs/MANIFEST.md +1 -1
- package/package.json +1 -1
- package/schema/manifest.schema.json +17 -0
- package/skills/pixelkiln/SKILL.md +3 -1
package/dist/index.js
CHANGED
|
@@ -193,6 +193,14 @@ var CharacterAnimationSchema = z.object({
|
|
|
193
193
|
template: z.string().min(1).optional(),
|
|
194
194
|
/** Direction to animate. */
|
|
195
195
|
direction: CharacterDirectionSchema.default("south"),
|
|
196
|
+
/**
|
|
197
|
+
* Shorthand for one loop in several directions, instead of `direction`.
|
|
198
|
+
* Expanded when the manifest loads into one loop per named direction
|
|
199
|
+
* (`<id>.<direction>`) plus a mirror for each direction whose flip is
|
|
200
|
+
* not named, exactly as if each had been written out. See
|
|
201
|
+
* `src/loop-directions.ts`.
|
|
202
|
+
*/
|
|
203
|
+
directions: z.array(CharacterDirectionSchema).min(1).optional(),
|
|
196
204
|
/** Frames to generate without a template (v3). Even, 4 to 16. */
|
|
197
205
|
frames: z.number().int().min(4).max(16).optional(),
|
|
198
206
|
/** Playback rate recorded with the frames; PixelLab does not store one. */
|
|
@@ -221,6 +229,13 @@ var CharacterAnimationSchema = z.object({
|
|
|
221
229
|
/** v3 text loops only: let PixelLab expand the action into a fuller motion description first. */
|
|
222
230
|
enhancePrompt: z.boolean().optional()
|
|
223
231
|
}).strict().superRefine((animation, context) => {
|
|
232
|
+
if (animation.directions !== void 0) {
|
|
233
|
+
context.addIssue({
|
|
234
|
+
code: z.ZodIssueCode.custom,
|
|
235
|
+
message: "directions is expanded when the manifest loads; validate the loaded manifest, not the raw file",
|
|
236
|
+
path: ["directions"]
|
|
237
|
+
});
|
|
238
|
+
}
|
|
224
239
|
if (animation.frames !== void 0 && animation.frames % 2 !== 0) {
|
|
225
240
|
context.addIssue({ code: z.ZodIssueCode.custom, message: "frames must be even", path: ["frames"] });
|
|
226
241
|
}
|
|
@@ -6992,6 +7007,91 @@ import { readFile as readFile3 } from "fs/promises";
|
|
|
6992
7007
|
import { existsSync as existsSync4 } from "fs";
|
|
6993
7008
|
import path5 from "path";
|
|
6994
7009
|
|
|
7010
|
+
// src/loop-directions.ts
|
|
7011
|
+
var SINGLE_OUTPUT_FIELDS = ["file", "cell", "remoteId", "source", "sourceByStyle", "outputRole"];
|
|
7012
|
+
var MIRROR_FIELDS = ["category", "tags", "styles"];
|
|
7013
|
+
function loopDirectionId(shorthandId, direction) {
|
|
7014
|
+
return `${shorthandId}.${direction}`;
|
|
7015
|
+
}
|
|
7016
|
+
function expandLoopDirections(raw) {
|
|
7017
|
+
const families = {};
|
|
7018
|
+
const issues = [];
|
|
7019
|
+
if (!isObject2(raw) || !isObject2(raw.assets)) return { raw, families, issues };
|
|
7020
|
+
const assets = raw.assets;
|
|
7021
|
+
if (!Object.values(assets).some((asset) => isObject2(asset) && isObject2(asset.animation) && "directions" in asset.animation)) {
|
|
7022
|
+
return { raw, families, issues };
|
|
7023
|
+
}
|
|
7024
|
+
const expanded = {};
|
|
7025
|
+
const claimed = new Set(Object.keys(assets));
|
|
7026
|
+
for (const [id, asset] of Object.entries(assets)) {
|
|
7027
|
+
if (!isObject2(asset) || !isObject2(asset.animation) || !("directions" in asset.animation)) {
|
|
7028
|
+
expanded[id] = asset;
|
|
7029
|
+
continue;
|
|
7030
|
+
}
|
|
7031
|
+
const where = `assets.${id}.animation.directions`;
|
|
7032
|
+
const { directions: rawDirections, ...animation } = asset.animation;
|
|
7033
|
+
const parsed = CharacterDirectionSchema.array().min(1).safeParse(rawDirections);
|
|
7034
|
+
if (!parsed.success) {
|
|
7035
|
+
issues.push(`${where}: expected a non-empty list of directions (${CharacterDirectionSchema.options.join(", ")})`);
|
|
7036
|
+
continue;
|
|
7037
|
+
}
|
|
7038
|
+
const directions = parsed.data;
|
|
7039
|
+
const duplicate = directions.find((direction, index) => directions.indexOf(direction) !== index);
|
|
7040
|
+
if (duplicate) {
|
|
7041
|
+
issues.push(`${where}: "${duplicate}" is listed twice`);
|
|
7042
|
+
continue;
|
|
7043
|
+
}
|
|
7044
|
+
if ("direction" in animation) {
|
|
7045
|
+
issues.push(`assets.${id}.animation: direction and directions are mutually exclusive; directions names every loop to make`);
|
|
7046
|
+
continue;
|
|
7047
|
+
}
|
|
7048
|
+
const single = SINGLE_OUTPUT_FIELDS.filter((field) => field in asset);
|
|
7049
|
+
if (single.length) {
|
|
7050
|
+
issues.push(
|
|
7051
|
+
`assets.${id}: ${single.join(", ")} name${single.length === 1 ? "s" : ""} one output, but directions makes ${directions.length} loops; declare the loops explicitly to set ${single.length === 1 ? "it" : "them"} per direction`
|
|
7052
|
+
);
|
|
7053
|
+
continue;
|
|
7054
|
+
}
|
|
7055
|
+
const loops = directions.map((direction) => ({ id: loopDirectionId(id, direction), direction }));
|
|
7056
|
+
const named = new Set(directions);
|
|
7057
|
+
const mirrors = [];
|
|
7058
|
+
for (const { id: loopId, direction } of loops) {
|
|
7059
|
+
const flipped = MIRRORED_DIRECTION[direction];
|
|
7060
|
+
if (flipped === direction || named.has(flipped)) continue;
|
|
7061
|
+
named.add(flipped);
|
|
7062
|
+
mirrors.push({ id: loopDirectionId(id, flipped), of: loopId });
|
|
7063
|
+
}
|
|
7064
|
+
const ids = [...loops.map((loop) => loop.id), ...mirrors.map((mirror) => mirror.id)];
|
|
7065
|
+
const collision = ids.find((expandedId) => claimed.has(expandedId));
|
|
7066
|
+
if (collision) {
|
|
7067
|
+
issues.push(`${where}: expands to "${collision}", which is already declared`);
|
|
7068
|
+
continue;
|
|
7069
|
+
}
|
|
7070
|
+
for (const expandedId of ids) claimed.add(expandedId);
|
|
7071
|
+
for (const loop of loops) {
|
|
7072
|
+
expanded[loop.id] = { ...asset, animation: { ...animation, direction: loop.direction } };
|
|
7073
|
+
}
|
|
7074
|
+
for (const mirror of mirrors) {
|
|
7075
|
+
const inherited = {};
|
|
7076
|
+
for (const field of MIRROR_FIELDS) if (field in asset) inherited[field] = asset[field];
|
|
7077
|
+
expanded[mirror.id] = { mirror: mirror.of, ...inherited };
|
|
7078
|
+
}
|
|
7079
|
+
families[id] = ids;
|
|
7080
|
+
}
|
|
7081
|
+
return { raw: { ...raw, assets: expanded }, families, issues };
|
|
7082
|
+
}
|
|
7083
|
+
function expandAssetFilter(ids, families) {
|
|
7084
|
+
if (!families) return [...ids];
|
|
7085
|
+
return [...new Set(ids.flatMap((id) => families[id] ?? [id]))];
|
|
7086
|
+
}
|
|
7087
|
+
function loopShorthandOf(assetId, families) {
|
|
7088
|
+
if (!families) return void 0;
|
|
7089
|
+
return Object.keys(families).find((id) => families[id].includes(assetId));
|
|
7090
|
+
}
|
|
7091
|
+
function isObject2(value) {
|
|
7092
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7093
|
+
}
|
|
7094
|
+
|
|
6995
7095
|
// src/outputs.ts
|
|
6996
7096
|
import { existsSync as existsSync3 } from "fs";
|
|
6997
7097
|
import path4 from "path";
|
|
@@ -7310,7 +7410,12 @@ function frameSetFps(entry) {
|
|
|
7310
7410
|
async function loadManifest(manifestPath) {
|
|
7311
7411
|
const abs = path5.resolve(manifestPath);
|
|
7312
7412
|
if (!existsSync4(abs)) throw new ProjectError(`No manifest at ${abs}`);
|
|
7313
|
-
const
|
|
7413
|
+
const expansion = expandLoopDirections(JSON.parse(await readFile3(abs, "utf8")));
|
|
7414
|
+
if (expansion.issues.length) {
|
|
7415
|
+
throw new ProjectError(`Manifest at ${abs} is invalid:
|
|
7416
|
+
${expansion.issues.map((i) => ` ${i}`).join("\n")}`);
|
|
7417
|
+
}
|
|
7418
|
+
const input = ManifestInputSchema.safeParse(expansion.raw);
|
|
7314
7419
|
if (!input.success) {
|
|
7315
7420
|
const issues = formatManifestIssues(input.error.issues);
|
|
7316
7421
|
throw new ProjectError(`Manifest at ${abs} is invalid:
|
|
@@ -7395,7 +7500,8 @@ ${issues}`);
|
|
|
7395
7500
|
throw new ProjectError(`Manifest at ${abs} is invalid:
|
|
7396
7501
|
${unknownReferences.map((i) => ` ${i}`).join("\n")}`);
|
|
7397
7502
|
}
|
|
7398
|
-
|
|
7503
|
+
const loopFamilies = Object.keys(expansion.families).length ? { loopFamilies: expansion.families } : {};
|
|
7504
|
+
return { manifest: parsed.data, root: path5.dirname(abs), path: abs, ...loopFamilies };
|
|
7399
7505
|
}
|
|
7400
7506
|
function parentAssetId(asset) {
|
|
7401
7507
|
return asset?.revision?.from ?? asset?.state?.of ?? asset?.animation?.of;
|
|
@@ -7479,12 +7585,13 @@ async function resolveSpecs(loaded, filter) {
|
|
|
7479
7585
|
);
|
|
7480
7586
|
}
|
|
7481
7587
|
}
|
|
7482
|
-
|
|
7588
|
+
const assetFilter = expandAssetFilter(filter?.assets ?? [], loaded.loopFamilies);
|
|
7589
|
+
for (const unknownAsset of assetFilter) {
|
|
7483
7590
|
if (!manifest.assets[unknownAsset]) {
|
|
7484
7591
|
throw new UsageError(`Unknown asset "${unknownAsset}".`);
|
|
7485
7592
|
}
|
|
7486
7593
|
}
|
|
7487
|
-
const requestedAssetIds = new Set(
|
|
7594
|
+
const requestedAssetIds = new Set(assetFilter.length ? assetFilter : Object.keys(manifest.assets));
|
|
7488
7595
|
const resolutionAssetIds = new Set(requestedAssetIds);
|
|
7489
7596
|
for (const assetId of [...requestedAssetIds]) {
|
|
7490
7597
|
let current = parentAssetId(manifest.assets[assetId]);
|
|
@@ -14208,11 +14315,21 @@ function assetStyles(raw, asset) {
|
|
|
14208
14315
|
const own = Array.isArray(asset.styles) ? asset.styles : [];
|
|
14209
14316
|
return own.length ? own : Object.keys(raw.styles ?? {});
|
|
14210
14317
|
}
|
|
14318
|
+
function declaredAssets(raw) {
|
|
14319
|
+
const expanded = expandLoopDirections(raw).raw;
|
|
14320
|
+
return new Set(Object.keys(expanded.assets ?? {}));
|
|
14321
|
+
}
|
|
14322
|
+
function undeclaredAsset(raw, assetId) {
|
|
14323
|
+
const shorthand = loopShorthandOf(assetId, expandLoopDirections(raw).families);
|
|
14324
|
+
return new ManifestEditError(
|
|
14325
|
+
shorthand ? `asset "${assetId}" is expanded from "${shorthand}"'s animation.directions; edit "${shorthand}" (it applies to every direction), or write this direction out as its own asset to change it alone` : `asset "${assetId}" is not declared by the manifest`
|
|
14326
|
+
);
|
|
14327
|
+
}
|
|
14211
14328
|
function applyEdit(raw, edit) {
|
|
14212
14329
|
raw.assets ??= {};
|
|
14213
14330
|
if (edit.action === "set-source" || edit.action === "clear-source") {
|
|
14214
14331
|
const asset2 = Object.hasOwn(raw.assets, edit.assetId) ? raw.assets[edit.assetId] : void 0;
|
|
14215
|
-
if (!asset2) throw
|
|
14332
|
+
if (!asset2) throw undeclaredAsset(raw, edit.assetId);
|
|
14216
14333
|
if (!raw.styles || !Object.hasOwn(raw.styles, edit.styleId)) {
|
|
14217
14334
|
throw new ManifestEditError(`unknown style "${edit.styleId}"`);
|
|
14218
14335
|
}
|
|
@@ -14264,7 +14381,7 @@ function applyEdit(raw, edit) {
|
|
|
14264
14381
|
return;
|
|
14265
14382
|
}
|
|
14266
14383
|
if (edit.action === "add-asset") {
|
|
14267
|
-
if (
|
|
14384
|
+
if (declaredAssets(raw).has(edit.assetId)) {
|
|
14268
14385
|
throw new ManifestEditError(`asset "${edit.assetId}" already exists`);
|
|
14269
14386
|
}
|
|
14270
14387
|
const { styles, tags, ...rest } = edit.asset;
|
|
@@ -14278,20 +14395,20 @@ function applyEdit(raw, edit) {
|
|
|
14278
14395
|
}
|
|
14279
14396
|
asset2.styles = styles;
|
|
14280
14397
|
}
|
|
14281
|
-
if (edit.asset.revision && !
|
|
14398
|
+
if (edit.asset.revision && !declaredAssets(raw).has(edit.asset.revision.from)) {
|
|
14282
14399
|
throw new ManifestEditError(`revision parent "${edit.asset.revision.from}" is not declared by the manifest`);
|
|
14283
14400
|
}
|
|
14284
|
-
if (edit.asset.state && !
|
|
14401
|
+
if (edit.asset.state && !declaredAssets(raw).has(edit.asset.state.of)) {
|
|
14285
14402
|
throw new ManifestEditError(`state parent "${edit.asset.state.of}" is not declared by the manifest`);
|
|
14286
14403
|
}
|
|
14287
|
-
if (edit.asset.animation && !
|
|
14404
|
+
if (edit.asset.animation && !declaredAssets(raw).has(edit.asset.animation.of)) {
|
|
14288
14405
|
throw new ManifestEditError(`animation parent "${edit.asset.animation.of}" is not declared by the manifest`);
|
|
14289
14406
|
}
|
|
14290
14407
|
raw.assets[edit.assetId] = asset2;
|
|
14291
14408
|
return;
|
|
14292
14409
|
}
|
|
14293
14410
|
const asset = Object.hasOwn(raw.assets, edit.assetId) ? raw.assets[edit.assetId] : void 0;
|
|
14294
|
-
if (!asset) throw
|
|
14411
|
+
if (!asset) throw undeclaredAsset(raw, edit.assetId);
|
|
14295
14412
|
const { patch } = edit;
|
|
14296
14413
|
if (patch.prompt !== void 0) asset.prompt = patch.prompt;
|
|
14297
14414
|
if (patch.promptForStyle) {
|
|
@@ -14905,6 +15022,7 @@ async function describeQuality(media, root, inspection) {
|
|
|
14905
15022
|
}
|
|
14906
15023
|
async function buildGallerySnapshot(opts) {
|
|
14907
15024
|
const { loaded, specs, lock } = opts;
|
|
15025
|
+
const filter = opts.filter?.assets?.length ? { ...opts.filter, assets: expandAssetFilter(opts.filter.assets, loaded.loopFamilies) } : opts.filter;
|
|
14908
15026
|
const root = loaded.root;
|
|
14909
15027
|
const media = /* @__PURE__ */ new Map();
|
|
14910
15028
|
const plan = await buildPlan(specs, lock);
|
|
@@ -15049,7 +15167,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
15049
15167
|
for (const [key, entry] of Object.entries(lock.entries)) {
|
|
15050
15168
|
if (shown.has(key)) continue;
|
|
15051
15169
|
if (isDeclared(loaded, entry.styleId, entry.assetId)) continue;
|
|
15052
|
-
if (!matchesFilter(
|
|
15170
|
+
if (!matchesFilter(filter, entry.styleId, entry.assetId)) continue;
|
|
15053
15171
|
const outputs = await Promise.all(
|
|
15054
15172
|
entry.outputs.map(
|
|
15055
15173
|
(output) => describeOutput(media, root, resolveOutputPath(output.path, root), output)
|
|
@@ -15215,7 +15333,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
15215
15333
|
try {
|
|
15216
15334
|
const loaded = await loadManifest(manifestPath);
|
|
15217
15335
|
const styleFilter = filter.styles.filter((id) => loaded.manifest.styles[id]);
|
|
15218
|
-
const assetFilter = filter.assets.filter((id) => loaded.manifest.assets[id]);
|
|
15336
|
+
const assetFilter = expandAssetFilter(filter.assets, loaded.loopFamilies).filter((id) => loaded.manifest.assets[id]);
|
|
15219
15337
|
const excluded = filter.styles.length > 0 && styleFilter.length === 0 || filter.assets.length > 0 && assetFilter.length === 0;
|
|
15220
15338
|
const lock = await loadLock(lockPath);
|
|
15221
15339
|
let projectItems = [];
|