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.cjs
CHANGED
|
@@ -511,6 +511,14 @@ var CharacterAnimationSchema = import_zod.z.object({
|
|
|
511
511
|
template: import_zod.z.string().min(1).optional(),
|
|
512
512
|
/** Direction to animate. */
|
|
513
513
|
direction: CharacterDirectionSchema.default("south"),
|
|
514
|
+
/**
|
|
515
|
+
* Shorthand for one loop in several directions, instead of `direction`.
|
|
516
|
+
* Expanded when the manifest loads into one loop per named direction
|
|
517
|
+
* (`<id>.<direction>`) plus a mirror for each direction whose flip is
|
|
518
|
+
* not named, exactly as if each had been written out. See
|
|
519
|
+
* `src/loop-directions.ts`.
|
|
520
|
+
*/
|
|
521
|
+
directions: import_zod.z.array(CharacterDirectionSchema).min(1).optional(),
|
|
514
522
|
/** Frames to generate without a template (v3). Even, 4 to 16. */
|
|
515
523
|
frames: import_zod.z.number().int().min(4).max(16).optional(),
|
|
516
524
|
/** Playback rate recorded with the frames; PixelLab does not store one. */
|
|
@@ -539,6 +547,13 @@ var CharacterAnimationSchema = import_zod.z.object({
|
|
|
539
547
|
/** v3 text loops only: let PixelLab expand the action into a fuller motion description first. */
|
|
540
548
|
enhancePrompt: import_zod.z.boolean().optional()
|
|
541
549
|
}).strict().superRefine((animation, context) => {
|
|
550
|
+
if (animation.directions !== void 0) {
|
|
551
|
+
context.addIssue({
|
|
552
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
553
|
+
message: "directions is expanded when the manifest loads; validate the loaded manifest, not the raw file",
|
|
554
|
+
path: ["directions"]
|
|
555
|
+
});
|
|
556
|
+
}
|
|
542
557
|
if (animation.frames !== void 0 && animation.frames % 2 !== 0) {
|
|
543
558
|
context.addIssue({ code: import_zod.z.ZodIssueCode.custom, message: "frames must be even", path: ["frames"] });
|
|
544
559
|
}
|
|
@@ -7310,6 +7325,91 @@ var import_promises3 = require("fs/promises");
|
|
|
7310
7325
|
var import_node_fs5 = require("fs");
|
|
7311
7326
|
var import_node_path5 = __toESM(require("path"), 1);
|
|
7312
7327
|
|
|
7328
|
+
// src/loop-directions.ts
|
|
7329
|
+
var SINGLE_OUTPUT_FIELDS = ["file", "cell", "remoteId", "source", "sourceByStyle", "outputRole"];
|
|
7330
|
+
var MIRROR_FIELDS = ["category", "tags", "styles"];
|
|
7331
|
+
function loopDirectionId(shorthandId, direction) {
|
|
7332
|
+
return `${shorthandId}.${direction}`;
|
|
7333
|
+
}
|
|
7334
|
+
function expandLoopDirections(raw) {
|
|
7335
|
+
const families = {};
|
|
7336
|
+
const issues = [];
|
|
7337
|
+
if (!isObject2(raw) || !isObject2(raw.assets)) return { raw, families, issues };
|
|
7338
|
+
const assets = raw.assets;
|
|
7339
|
+
if (!Object.values(assets).some((asset) => isObject2(asset) && isObject2(asset.animation) && "directions" in asset.animation)) {
|
|
7340
|
+
return { raw, families, issues };
|
|
7341
|
+
}
|
|
7342
|
+
const expanded = {};
|
|
7343
|
+
const claimed = new Set(Object.keys(assets));
|
|
7344
|
+
for (const [id, asset] of Object.entries(assets)) {
|
|
7345
|
+
if (!isObject2(asset) || !isObject2(asset.animation) || !("directions" in asset.animation)) {
|
|
7346
|
+
expanded[id] = asset;
|
|
7347
|
+
continue;
|
|
7348
|
+
}
|
|
7349
|
+
const where = `assets.${id}.animation.directions`;
|
|
7350
|
+
const { directions: rawDirections, ...animation } = asset.animation;
|
|
7351
|
+
const parsed = CharacterDirectionSchema.array().min(1).safeParse(rawDirections);
|
|
7352
|
+
if (!parsed.success) {
|
|
7353
|
+
issues.push(`${where}: expected a non-empty list of directions (${CharacterDirectionSchema.options.join(", ")})`);
|
|
7354
|
+
continue;
|
|
7355
|
+
}
|
|
7356
|
+
const directions = parsed.data;
|
|
7357
|
+
const duplicate = directions.find((direction, index) => directions.indexOf(direction) !== index);
|
|
7358
|
+
if (duplicate) {
|
|
7359
|
+
issues.push(`${where}: "${duplicate}" is listed twice`);
|
|
7360
|
+
continue;
|
|
7361
|
+
}
|
|
7362
|
+
if ("direction" in animation) {
|
|
7363
|
+
issues.push(`assets.${id}.animation: direction and directions are mutually exclusive; directions names every loop to make`);
|
|
7364
|
+
continue;
|
|
7365
|
+
}
|
|
7366
|
+
const single = SINGLE_OUTPUT_FIELDS.filter((field) => field in asset);
|
|
7367
|
+
if (single.length) {
|
|
7368
|
+
issues.push(
|
|
7369
|
+
`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`
|
|
7370
|
+
);
|
|
7371
|
+
continue;
|
|
7372
|
+
}
|
|
7373
|
+
const loops = directions.map((direction) => ({ id: loopDirectionId(id, direction), direction }));
|
|
7374
|
+
const named = new Set(directions);
|
|
7375
|
+
const mirrors = [];
|
|
7376
|
+
for (const { id: loopId, direction } of loops) {
|
|
7377
|
+
const flipped = MIRRORED_DIRECTION[direction];
|
|
7378
|
+
if (flipped === direction || named.has(flipped)) continue;
|
|
7379
|
+
named.add(flipped);
|
|
7380
|
+
mirrors.push({ id: loopDirectionId(id, flipped), of: loopId });
|
|
7381
|
+
}
|
|
7382
|
+
const ids = [...loops.map((loop) => loop.id), ...mirrors.map((mirror) => mirror.id)];
|
|
7383
|
+
const collision = ids.find((expandedId) => claimed.has(expandedId));
|
|
7384
|
+
if (collision) {
|
|
7385
|
+
issues.push(`${where}: expands to "${collision}", which is already declared`);
|
|
7386
|
+
continue;
|
|
7387
|
+
}
|
|
7388
|
+
for (const expandedId of ids) claimed.add(expandedId);
|
|
7389
|
+
for (const loop of loops) {
|
|
7390
|
+
expanded[loop.id] = { ...asset, animation: { ...animation, direction: loop.direction } };
|
|
7391
|
+
}
|
|
7392
|
+
for (const mirror of mirrors) {
|
|
7393
|
+
const inherited = {};
|
|
7394
|
+
for (const field of MIRROR_FIELDS) if (field in asset) inherited[field] = asset[field];
|
|
7395
|
+
expanded[mirror.id] = { mirror: mirror.of, ...inherited };
|
|
7396
|
+
}
|
|
7397
|
+
families[id] = ids;
|
|
7398
|
+
}
|
|
7399
|
+
return { raw: { ...raw, assets: expanded }, families, issues };
|
|
7400
|
+
}
|
|
7401
|
+
function expandAssetFilter(ids, families) {
|
|
7402
|
+
if (!families) return [...ids];
|
|
7403
|
+
return [...new Set(ids.flatMap((id) => families[id] ?? [id]))];
|
|
7404
|
+
}
|
|
7405
|
+
function loopShorthandOf(assetId, families) {
|
|
7406
|
+
if (!families) return void 0;
|
|
7407
|
+
return Object.keys(families).find((id) => families[id].includes(assetId));
|
|
7408
|
+
}
|
|
7409
|
+
function isObject2(value) {
|
|
7410
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7411
|
+
}
|
|
7412
|
+
|
|
7313
7413
|
// src/outputs.ts
|
|
7314
7414
|
var import_node_fs4 = require("fs");
|
|
7315
7415
|
var import_node_path4 = __toESM(require("path"), 1);
|
|
@@ -7628,7 +7728,12 @@ function frameSetFps(entry) {
|
|
|
7628
7728
|
async function loadManifest(manifestPath) {
|
|
7629
7729
|
const abs = import_node_path5.default.resolve(manifestPath);
|
|
7630
7730
|
if (!(0, import_node_fs5.existsSync)(abs)) throw new ProjectError(`No manifest at ${abs}`);
|
|
7631
|
-
const
|
|
7731
|
+
const expansion = expandLoopDirections(JSON.parse(await (0, import_promises3.readFile)(abs, "utf8")));
|
|
7732
|
+
if (expansion.issues.length) {
|
|
7733
|
+
throw new ProjectError(`Manifest at ${abs} is invalid:
|
|
7734
|
+
${expansion.issues.map((i) => ` ${i}`).join("\n")}`);
|
|
7735
|
+
}
|
|
7736
|
+
const input = ManifestInputSchema.safeParse(expansion.raw);
|
|
7632
7737
|
if (!input.success) {
|
|
7633
7738
|
const issues = formatManifestIssues(input.error.issues);
|
|
7634
7739
|
throw new ProjectError(`Manifest at ${abs} is invalid:
|
|
@@ -7713,7 +7818,8 @@ ${issues}`);
|
|
|
7713
7818
|
throw new ProjectError(`Manifest at ${abs} is invalid:
|
|
7714
7819
|
${unknownReferences.map((i) => ` ${i}`).join("\n")}`);
|
|
7715
7820
|
}
|
|
7716
|
-
|
|
7821
|
+
const loopFamilies = Object.keys(expansion.families).length ? { loopFamilies: expansion.families } : {};
|
|
7822
|
+
return { manifest: parsed.data, root: import_node_path5.default.dirname(abs), path: abs, ...loopFamilies };
|
|
7717
7823
|
}
|
|
7718
7824
|
function parentAssetId(asset) {
|
|
7719
7825
|
return asset?.revision?.from ?? asset?.state?.of ?? asset?.animation?.of;
|
|
@@ -7797,12 +7903,13 @@ async function resolveSpecs(loaded, filter) {
|
|
|
7797
7903
|
);
|
|
7798
7904
|
}
|
|
7799
7905
|
}
|
|
7800
|
-
|
|
7906
|
+
const assetFilter = expandAssetFilter(filter?.assets ?? [], loaded.loopFamilies);
|
|
7907
|
+
for (const unknownAsset of assetFilter) {
|
|
7801
7908
|
if (!manifest.assets[unknownAsset]) {
|
|
7802
7909
|
throw new UsageError(`Unknown asset "${unknownAsset}".`);
|
|
7803
7910
|
}
|
|
7804
7911
|
}
|
|
7805
|
-
const requestedAssetIds = new Set(
|
|
7912
|
+
const requestedAssetIds = new Set(assetFilter.length ? assetFilter : Object.keys(manifest.assets));
|
|
7806
7913
|
const resolutionAssetIds = new Set(requestedAssetIds);
|
|
7807
7914
|
for (const assetId of [...requestedAssetIds]) {
|
|
7808
7915
|
let current = parentAssetId(manifest.assets[assetId]);
|
|
@@ -14526,11 +14633,21 @@ function assetStyles(raw, asset) {
|
|
|
14526
14633
|
const own = Array.isArray(asset.styles) ? asset.styles : [];
|
|
14527
14634
|
return own.length ? own : Object.keys(raw.styles ?? {});
|
|
14528
14635
|
}
|
|
14636
|
+
function declaredAssets(raw) {
|
|
14637
|
+
const expanded = expandLoopDirections(raw).raw;
|
|
14638
|
+
return new Set(Object.keys(expanded.assets ?? {}));
|
|
14639
|
+
}
|
|
14640
|
+
function undeclaredAsset(raw, assetId) {
|
|
14641
|
+
const shorthand = loopShorthandOf(assetId, expandLoopDirections(raw).families);
|
|
14642
|
+
return new ManifestEditError(
|
|
14643
|
+
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`
|
|
14644
|
+
);
|
|
14645
|
+
}
|
|
14529
14646
|
function applyEdit(raw, edit) {
|
|
14530
14647
|
raw.assets ??= {};
|
|
14531
14648
|
if (edit.action === "set-source" || edit.action === "clear-source") {
|
|
14532
14649
|
const asset2 = Object.hasOwn(raw.assets, edit.assetId) ? raw.assets[edit.assetId] : void 0;
|
|
14533
|
-
if (!asset2) throw
|
|
14650
|
+
if (!asset2) throw undeclaredAsset(raw, edit.assetId);
|
|
14534
14651
|
if (!raw.styles || !Object.hasOwn(raw.styles, edit.styleId)) {
|
|
14535
14652
|
throw new ManifestEditError(`unknown style "${edit.styleId}"`);
|
|
14536
14653
|
}
|
|
@@ -14582,7 +14699,7 @@ function applyEdit(raw, edit) {
|
|
|
14582
14699
|
return;
|
|
14583
14700
|
}
|
|
14584
14701
|
if (edit.action === "add-asset") {
|
|
14585
|
-
if (
|
|
14702
|
+
if (declaredAssets(raw).has(edit.assetId)) {
|
|
14586
14703
|
throw new ManifestEditError(`asset "${edit.assetId}" already exists`);
|
|
14587
14704
|
}
|
|
14588
14705
|
const { styles, tags, ...rest } = edit.asset;
|
|
@@ -14596,20 +14713,20 @@ function applyEdit(raw, edit) {
|
|
|
14596
14713
|
}
|
|
14597
14714
|
asset2.styles = styles;
|
|
14598
14715
|
}
|
|
14599
|
-
if (edit.asset.revision && !
|
|
14716
|
+
if (edit.asset.revision && !declaredAssets(raw).has(edit.asset.revision.from)) {
|
|
14600
14717
|
throw new ManifestEditError(`revision parent "${edit.asset.revision.from}" is not declared by the manifest`);
|
|
14601
14718
|
}
|
|
14602
|
-
if (edit.asset.state && !
|
|
14719
|
+
if (edit.asset.state && !declaredAssets(raw).has(edit.asset.state.of)) {
|
|
14603
14720
|
throw new ManifestEditError(`state parent "${edit.asset.state.of}" is not declared by the manifest`);
|
|
14604
14721
|
}
|
|
14605
|
-
if (edit.asset.animation && !
|
|
14722
|
+
if (edit.asset.animation && !declaredAssets(raw).has(edit.asset.animation.of)) {
|
|
14606
14723
|
throw new ManifestEditError(`animation parent "${edit.asset.animation.of}" is not declared by the manifest`);
|
|
14607
14724
|
}
|
|
14608
14725
|
raw.assets[edit.assetId] = asset2;
|
|
14609
14726
|
return;
|
|
14610
14727
|
}
|
|
14611
14728
|
const asset = Object.hasOwn(raw.assets, edit.assetId) ? raw.assets[edit.assetId] : void 0;
|
|
14612
|
-
if (!asset) throw
|
|
14729
|
+
if (!asset) throw undeclaredAsset(raw, edit.assetId);
|
|
14613
14730
|
const { patch } = edit;
|
|
14614
14731
|
if (patch.prompt !== void 0) asset.prompt = patch.prompt;
|
|
14615
14732
|
if (patch.promptForStyle) {
|
|
@@ -15223,6 +15340,7 @@ async function describeQuality(media, root, inspection) {
|
|
|
15223
15340
|
}
|
|
15224
15341
|
async function buildGallerySnapshot(opts) {
|
|
15225
15342
|
const { loaded, specs, lock } = opts;
|
|
15343
|
+
const filter = opts.filter?.assets?.length ? { ...opts.filter, assets: expandAssetFilter(opts.filter.assets, loaded.loopFamilies) } : opts.filter;
|
|
15226
15344
|
const root = loaded.root;
|
|
15227
15345
|
const media = /* @__PURE__ */ new Map();
|
|
15228
15346
|
const plan = await buildPlan(specs, lock);
|
|
@@ -15367,7 +15485,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
15367
15485
|
for (const [key, entry] of Object.entries(lock.entries)) {
|
|
15368
15486
|
if (shown.has(key)) continue;
|
|
15369
15487
|
if (isDeclared(loaded, entry.styleId, entry.assetId)) continue;
|
|
15370
|
-
if (!matchesFilter(
|
|
15488
|
+
if (!matchesFilter(filter, entry.styleId, entry.assetId)) continue;
|
|
15371
15489
|
const outputs = await Promise.all(
|
|
15372
15490
|
entry.outputs.map(
|
|
15373
15491
|
(output) => describeOutput(media, root, resolveOutputPath(output.path, root), output)
|
|
@@ -15533,7 +15651,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
15533
15651
|
try {
|
|
15534
15652
|
const loaded = await loadManifest(manifestPath);
|
|
15535
15653
|
const styleFilter = filter.styles.filter((id) => loaded.manifest.styles[id]);
|
|
15536
|
-
const assetFilter = filter.assets.filter((id) => loaded.manifest.assets[id]);
|
|
15654
|
+
const assetFilter = expandAssetFilter(filter.assets, loaded.loopFamilies).filter((id) => loaded.manifest.assets[id]);
|
|
15537
15655
|
const excluded = filter.styles.length > 0 && styleFilter.length === 0 || filter.assets.length > 0 && assetFilter.length === 0;
|
|
15538
15656
|
const lock = await loadLock(lockPath);
|
|
15539
15657
|
let projectItems = [];
|