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/cli.js
CHANGED
|
@@ -956,6 +956,14 @@ var CharacterAnimationSchema = z.object({
|
|
|
956
956
|
template: z.string().min(1).optional(),
|
|
957
957
|
/** Direction to animate. */
|
|
958
958
|
direction: CharacterDirectionSchema.default("south"),
|
|
959
|
+
/**
|
|
960
|
+
* Shorthand for one loop in several directions, instead of `direction`.
|
|
961
|
+
* Expanded when the manifest loads into one loop per named direction
|
|
962
|
+
* (`<id>.<direction>`) plus a mirror for each direction whose flip is
|
|
963
|
+
* not named, exactly as if each had been written out. See
|
|
964
|
+
* `src/loop-directions.ts`.
|
|
965
|
+
*/
|
|
966
|
+
directions: z.array(CharacterDirectionSchema).min(1).optional(),
|
|
959
967
|
/** Frames to generate without a template (v3). Even, 4 to 16. */
|
|
960
968
|
frames: z.number().int().min(4).max(16).optional(),
|
|
961
969
|
/** Playback rate recorded with the frames; PixelLab does not store one. */
|
|
@@ -984,6 +992,13 @@ var CharacterAnimationSchema = z.object({
|
|
|
984
992
|
/** v3 text loops only: let PixelLab expand the action into a fuller motion description first. */
|
|
985
993
|
enhancePrompt: z.boolean().optional()
|
|
986
994
|
}).strict().superRefine((animation, context) => {
|
|
995
|
+
if (animation.directions !== void 0) {
|
|
996
|
+
context.addIssue({
|
|
997
|
+
code: z.ZodIssueCode.custom,
|
|
998
|
+
message: "directions is expanded when the manifest loads; validate the loaded manifest, not the raw file",
|
|
999
|
+
path: ["directions"]
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
987
1002
|
if (animation.frames !== void 0 && animation.frames % 2 !== 0) {
|
|
988
1003
|
context.addIssue({ code: z.ZodIssueCode.custom, message: "frames must be even", path: ["frames"] });
|
|
989
1004
|
}
|
|
@@ -9549,10 +9564,102 @@ registerProvider({
|
|
|
9549
9564
|
import { readFile as readFile6 } from "fs/promises";
|
|
9550
9565
|
import { existsSync as existsSync8 } from "fs";
|
|
9551
9566
|
import path13 from "path";
|
|
9567
|
+
|
|
9568
|
+
// src/loop-directions.ts
|
|
9569
|
+
var SINGLE_OUTPUT_FIELDS = ["file", "cell", "remoteId", "source", "sourceByStyle", "outputRole"];
|
|
9570
|
+
var MIRROR_FIELDS = ["category", "tags", "styles"];
|
|
9571
|
+
function loopDirectionId(shorthandId, direction) {
|
|
9572
|
+
return `${shorthandId}.${direction}`;
|
|
9573
|
+
}
|
|
9574
|
+
function expandLoopDirections(raw) {
|
|
9575
|
+
const families = {};
|
|
9576
|
+
const issues = [];
|
|
9577
|
+
if (!isObject2(raw) || !isObject2(raw.assets)) return { raw, families, issues };
|
|
9578
|
+
const assets = raw.assets;
|
|
9579
|
+
if (!Object.values(assets).some((asset) => isObject2(asset) && isObject2(asset.animation) && "directions" in asset.animation)) {
|
|
9580
|
+
return { raw, families, issues };
|
|
9581
|
+
}
|
|
9582
|
+
const expanded = {};
|
|
9583
|
+
const claimed = new Set(Object.keys(assets));
|
|
9584
|
+
for (const [id, asset] of Object.entries(assets)) {
|
|
9585
|
+
if (!isObject2(asset) || !isObject2(asset.animation) || !("directions" in asset.animation)) {
|
|
9586
|
+
expanded[id] = asset;
|
|
9587
|
+
continue;
|
|
9588
|
+
}
|
|
9589
|
+
const where = `assets.${id}.animation.directions`;
|
|
9590
|
+
const { directions: rawDirections, ...animation } = asset.animation;
|
|
9591
|
+
const parsed = CharacterDirectionSchema.array().min(1).safeParse(rawDirections);
|
|
9592
|
+
if (!parsed.success) {
|
|
9593
|
+
issues.push(`${where}: expected a non-empty list of directions (${CharacterDirectionSchema.options.join(", ")})`);
|
|
9594
|
+
continue;
|
|
9595
|
+
}
|
|
9596
|
+
const directions = parsed.data;
|
|
9597
|
+
const duplicate = directions.find((direction, index) => directions.indexOf(direction) !== index);
|
|
9598
|
+
if (duplicate) {
|
|
9599
|
+
issues.push(`${where}: "${duplicate}" is listed twice`);
|
|
9600
|
+
continue;
|
|
9601
|
+
}
|
|
9602
|
+
if ("direction" in animation) {
|
|
9603
|
+
issues.push(`assets.${id}.animation: direction and directions are mutually exclusive; directions names every loop to make`);
|
|
9604
|
+
continue;
|
|
9605
|
+
}
|
|
9606
|
+
const single = SINGLE_OUTPUT_FIELDS.filter((field) => field in asset);
|
|
9607
|
+
if (single.length) {
|
|
9608
|
+
issues.push(
|
|
9609
|
+
`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`
|
|
9610
|
+
);
|
|
9611
|
+
continue;
|
|
9612
|
+
}
|
|
9613
|
+
const loops = directions.map((direction) => ({ id: loopDirectionId(id, direction), direction }));
|
|
9614
|
+
const named = new Set(directions);
|
|
9615
|
+
const mirrors = [];
|
|
9616
|
+
for (const { id: loopId, direction } of loops) {
|
|
9617
|
+
const flipped = MIRRORED_DIRECTION[direction];
|
|
9618
|
+
if (flipped === direction || named.has(flipped)) continue;
|
|
9619
|
+
named.add(flipped);
|
|
9620
|
+
mirrors.push({ id: loopDirectionId(id, flipped), of: loopId });
|
|
9621
|
+
}
|
|
9622
|
+
const ids = [...loops.map((loop) => loop.id), ...mirrors.map((mirror) => mirror.id)];
|
|
9623
|
+
const collision = ids.find((expandedId) => claimed.has(expandedId));
|
|
9624
|
+
if (collision) {
|
|
9625
|
+
issues.push(`${where}: expands to "${collision}", which is already declared`);
|
|
9626
|
+
continue;
|
|
9627
|
+
}
|
|
9628
|
+
for (const expandedId of ids) claimed.add(expandedId);
|
|
9629
|
+
for (const loop of loops) {
|
|
9630
|
+
expanded[loop.id] = { ...asset, animation: { ...animation, direction: loop.direction } };
|
|
9631
|
+
}
|
|
9632
|
+
for (const mirror of mirrors) {
|
|
9633
|
+
const inherited = {};
|
|
9634
|
+
for (const field of MIRROR_FIELDS) if (field in asset) inherited[field] = asset[field];
|
|
9635
|
+
expanded[mirror.id] = { mirror: mirror.of, ...inherited };
|
|
9636
|
+
}
|
|
9637
|
+
families[id] = ids;
|
|
9638
|
+
}
|
|
9639
|
+
return { raw: { ...raw, assets: expanded }, families, issues };
|
|
9640
|
+
}
|
|
9641
|
+
function expandAssetFilter(ids, families) {
|
|
9642
|
+
if (!families) return [...ids];
|
|
9643
|
+
return [...new Set(ids.flatMap((id) => families[id] ?? [id]))];
|
|
9644
|
+
}
|
|
9645
|
+
function loopShorthandOf(assetId, families) {
|
|
9646
|
+
if (!families) return void 0;
|
|
9647
|
+
return Object.keys(families).find((id) => families[id].includes(assetId));
|
|
9648
|
+
}
|
|
9649
|
+
function isObject2(value) {
|
|
9650
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9651
|
+
}
|
|
9652
|
+
|
|
9653
|
+
// src/manifest.ts
|
|
9552
9654
|
async function loadManifest(manifestPath) {
|
|
9553
9655
|
const abs = path13.resolve(manifestPath);
|
|
9554
9656
|
if (!existsSync8(abs)) throw new ProjectError(`No manifest at ${abs}`);
|
|
9555
|
-
const
|
|
9657
|
+
const expansion = expandLoopDirections(JSON.parse(await readFile6(abs, "utf8")));
|
|
9658
|
+
if (expansion.issues.length) {
|
|
9659
|
+
throw new ProjectError(`Manifest at ${abs} is invalid:
|
|
9660
|
+
${expansion.issues.map((i) => ` ${i}`).join("\n")}`);
|
|
9661
|
+
}
|
|
9662
|
+
const input = ManifestInputSchema.safeParse(expansion.raw);
|
|
9556
9663
|
if (!input.success) {
|
|
9557
9664
|
const issues = formatManifestIssues(input.error.issues);
|
|
9558
9665
|
throw new ProjectError(`Manifest at ${abs} is invalid:
|
|
@@ -9637,7 +9744,8 @@ ${issues}`);
|
|
|
9637
9744
|
throw new ProjectError(`Manifest at ${abs} is invalid:
|
|
9638
9745
|
${unknownReferences.map((i) => ` ${i}`).join("\n")}`);
|
|
9639
9746
|
}
|
|
9640
|
-
|
|
9747
|
+
const loopFamilies = Object.keys(expansion.families).length ? { loopFamilies: expansion.families } : {};
|
|
9748
|
+
return { manifest: parsed.data, root: path13.dirname(abs), path: abs, ...loopFamilies };
|
|
9641
9749
|
}
|
|
9642
9750
|
function parentAssetId(asset) {
|
|
9643
9751
|
return asset?.revision?.from ?? asset?.state?.of ?? asset?.animation?.of;
|
|
@@ -9721,12 +9829,13 @@ async function resolveSpecs(loaded, filter) {
|
|
|
9721
9829
|
);
|
|
9722
9830
|
}
|
|
9723
9831
|
}
|
|
9724
|
-
|
|
9832
|
+
const assetFilter = expandAssetFilter(filter?.assets ?? [], loaded.loopFamilies);
|
|
9833
|
+
for (const unknownAsset of assetFilter) {
|
|
9725
9834
|
if (!manifest.assets[unknownAsset]) {
|
|
9726
9835
|
throw new UsageError(`Unknown asset "${unknownAsset}".`);
|
|
9727
9836
|
}
|
|
9728
9837
|
}
|
|
9729
|
-
const requestedAssetIds = new Set(
|
|
9838
|
+
const requestedAssetIds = new Set(assetFilter.length ? assetFilter : Object.keys(manifest.assets));
|
|
9730
9839
|
const resolutionAssetIds = new Set(requestedAssetIds);
|
|
9731
9840
|
for (const assetId of [...requestedAssetIds]) {
|
|
9732
9841
|
let current = parentAssetId(manifest.assets[assetId]);
|
|
@@ -12733,11 +12842,21 @@ function assetStyles(raw, asset) {
|
|
|
12733
12842
|
const own = Array.isArray(asset.styles) ? asset.styles : [];
|
|
12734
12843
|
return own.length ? own : Object.keys(raw.styles ?? {});
|
|
12735
12844
|
}
|
|
12845
|
+
function declaredAssets(raw) {
|
|
12846
|
+
const expanded = expandLoopDirections(raw).raw;
|
|
12847
|
+
return new Set(Object.keys(expanded.assets ?? {}));
|
|
12848
|
+
}
|
|
12849
|
+
function undeclaredAsset(raw, assetId) {
|
|
12850
|
+
const shorthand = loopShorthandOf(assetId, expandLoopDirections(raw).families);
|
|
12851
|
+
return new ManifestEditError(
|
|
12852
|
+
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`
|
|
12853
|
+
);
|
|
12854
|
+
}
|
|
12736
12855
|
function applyEdit(raw, edit) {
|
|
12737
12856
|
raw.assets ??= {};
|
|
12738
12857
|
if (edit.action === "set-source" || edit.action === "clear-source") {
|
|
12739
12858
|
const asset2 = Object.hasOwn(raw.assets, edit.assetId) ? raw.assets[edit.assetId] : void 0;
|
|
12740
|
-
if (!asset2) throw
|
|
12859
|
+
if (!asset2) throw undeclaredAsset(raw, edit.assetId);
|
|
12741
12860
|
if (!raw.styles || !Object.hasOwn(raw.styles, edit.styleId)) {
|
|
12742
12861
|
throw new ManifestEditError(`unknown style "${edit.styleId}"`);
|
|
12743
12862
|
}
|
|
@@ -12789,7 +12908,7 @@ function applyEdit(raw, edit) {
|
|
|
12789
12908
|
return;
|
|
12790
12909
|
}
|
|
12791
12910
|
if (edit.action === "add-asset") {
|
|
12792
|
-
if (
|
|
12911
|
+
if (declaredAssets(raw).has(edit.assetId)) {
|
|
12793
12912
|
throw new ManifestEditError(`asset "${edit.assetId}" already exists`);
|
|
12794
12913
|
}
|
|
12795
12914
|
const { styles, tags, ...rest } = edit.asset;
|
|
@@ -12803,20 +12922,20 @@ function applyEdit(raw, edit) {
|
|
|
12803
12922
|
}
|
|
12804
12923
|
asset2.styles = styles;
|
|
12805
12924
|
}
|
|
12806
|
-
if (edit.asset.revision && !
|
|
12925
|
+
if (edit.asset.revision && !declaredAssets(raw).has(edit.asset.revision.from)) {
|
|
12807
12926
|
throw new ManifestEditError(`revision parent "${edit.asset.revision.from}" is not declared by the manifest`);
|
|
12808
12927
|
}
|
|
12809
|
-
if (edit.asset.state && !
|
|
12928
|
+
if (edit.asset.state && !declaredAssets(raw).has(edit.asset.state.of)) {
|
|
12810
12929
|
throw new ManifestEditError(`state parent "${edit.asset.state.of}" is not declared by the manifest`);
|
|
12811
12930
|
}
|
|
12812
|
-
if (edit.asset.animation && !
|
|
12931
|
+
if (edit.asset.animation && !declaredAssets(raw).has(edit.asset.animation.of)) {
|
|
12813
12932
|
throw new ManifestEditError(`animation parent "${edit.asset.animation.of}" is not declared by the manifest`);
|
|
12814
12933
|
}
|
|
12815
12934
|
raw.assets[edit.assetId] = asset2;
|
|
12816
12935
|
return;
|
|
12817
12936
|
}
|
|
12818
12937
|
const asset = Object.hasOwn(raw.assets, edit.assetId) ? raw.assets[edit.assetId] : void 0;
|
|
12819
|
-
if (!asset) throw
|
|
12938
|
+
if (!asset) throw undeclaredAsset(raw, edit.assetId);
|
|
12820
12939
|
const { patch } = edit;
|
|
12821
12940
|
if (patch.prompt !== void 0) asset.prompt = patch.prompt;
|
|
12822
12941
|
if (patch.promptForStyle) {
|
|
@@ -13604,6 +13723,7 @@ async function describeQuality(media, root, inspection) {
|
|
|
13604
13723
|
}
|
|
13605
13724
|
async function buildGallerySnapshot(opts) {
|
|
13606
13725
|
const { loaded, specs, lock } = opts;
|
|
13726
|
+
const filter = opts.filter?.assets?.length ? { ...opts.filter, assets: expandAssetFilter(opts.filter.assets, loaded.loopFamilies) } : opts.filter;
|
|
13607
13727
|
const root = loaded.root;
|
|
13608
13728
|
const media = /* @__PURE__ */ new Map();
|
|
13609
13729
|
const plan = await buildPlan(specs, lock);
|
|
@@ -13748,7 +13868,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
13748
13868
|
for (const [key, entry] of Object.entries(lock.entries)) {
|
|
13749
13869
|
if (shown.has(key)) continue;
|
|
13750
13870
|
if (isDeclared(loaded, entry.styleId, entry.assetId)) continue;
|
|
13751
|
-
if (!matchesFilter(
|
|
13871
|
+
if (!matchesFilter(filter, entry.styleId, entry.assetId)) continue;
|
|
13752
13872
|
const outputs = await Promise.all(
|
|
13753
13873
|
entry.outputs.map(
|
|
13754
13874
|
(output) => describeOutput(media, root, resolveOutputPath(output.path, root), output)
|
|
@@ -13914,7 +14034,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
13914
14034
|
try {
|
|
13915
14035
|
const loaded = await loadManifest(manifestPath);
|
|
13916
14036
|
const styleFilter = filter.styles.filter((id) => loaded.manifest.styles[id]);
|
|
13917
|
-
const assetFilter = filter.assets.filter((id) => loaded.manifest.assets[id]);
|
|
14037
|
+
const assetFilter = expandAssetFilter(filter.assets, loaded.loopFamilies).filter((id) => loaded.manifest.assets[id]);
|
|
13918
14038
|
const excluded = filter.styles.length > 0 && styleFilter.length === 0 || filter.assets.length > 0 && assetFilter.length === 0;
|
|
13919
14039
|
const lock = await loadLock(lockPath);
|
|
13920
14040
|
let projectItems = [];
|