pixelkiln 0.16.0 → 0.17.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.d.ts +16 -1
- package/dist/cli.js +155 -28
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +109 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +149 -7
- package/dist/index.d.ts +149 -7
- package/dist/index.js +107 -21
- package/dist/index.js.map +1 -1
- package/docs/AGENTS.md +4 -2
- package/docs/ARCHITECTURE.md +5 -1
- package/docs/CLI.md +26 -3
- package/docs/COMFYUI.md +5 -3
- package/docs/GETTING_STARTED.md +8 -1
- package/docs/LIBRARY.md +12 -0
- package/docs/MANIFEST.md +6 -3
- package/docs/QUALITY.md +13 -8
- package/docs/RECOVERY.md +17 -0
- package/package.json +1 -1
- package/schema/manifest.schema.json +4 -0
- package/schema/recipe.schema.json +4 -0
- package/skills/pixelkiln/SKILL.md +6 -3
- package/skills/pixelkiln/references/quality.md +9 -2
package/README.md
CHANGED
|
@@ -55,10 +55,10 @@ PixelKiln keeps the missing record:
|
|
|
55
55
|
| Workflow | What PixelKiln provides |
|
|
56
56
|
|---|---|
|
|
57
57
|
| Plan and budget | Offline manifest/lock/disk diff, provider-grouped estimates, keyed mixed-provider budget ceilings, JSON/CI gate. |
|
|
58
|
-
| Generate and review | Resumable submit/poll/pick/fetch pipeline
|
|
58
|
+
| Generate and review | Resumable submit/poll/pick/fetch pipeline, exact next-step hints, and a fast local candidate sheet. |
|
|
59
59
|
| Controlled revisions | Hashed image-to-image/inpaint lineage, fail-closed parent approval, and source-versus-candidate review; ComfyUI is the first adapter. |
|
|
60
60
|
| Existing-art onboarding | Manifest scaffolding, exact-hash account adoption, and prompt recovery. |
|
|
61
|
-
| Recovery |
|
|
61
|
+
| Recovery | Safe stale-output replacement, validated caches, durable references, and resumable paid jobs. |
|
|
62
62
|
| Shared-account safety | Cross-project claim files or a registered workspace catalog, sibling-style exclusion, reviewed salvage, keep/discard tags, separate confirmed purge. |
|
|
63
63
|
| Quality control | Manifest-native grid recovery, closed palettes, named approval, regression baselines, and fail-closed packaging. |
|
|
64
64
|
| Sprite packaging | Deterministic RGBA packing, stable-cell mounting, explicit external input lists, structural output roles. |
|
package/dist/cli.d.ts
CHANGED
|
@@ -4,8 +4,23 @@ import { z } from 'zod';
|
|
|
4
4
|
declare const GridConfidenceSchema: z.ZodEnum<["low", "medium", "high"]>;
|
|
5
5
|
type GridConfidence = z.infer<typeof GridConfidenceSchema>;
|
|
6
6
|
|
|
7
|
+
interface ReviewReadyInfo {
|
|
8
|
+
url: string;
|
|
9
|
+
keys: string[];
|
|
10
|
+
}
|
|
11
|
+
|
|
7
12
|
type TilesetFormat = "generic" | "tiled" | "godot";
|
|
8
13
|
|
|
14
|
+
interface CliWritable {
|
|
15
|
+
isTTY?: boolean;
|
|
16
|
+
write(chunk: string): unknown;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Interactive readiness is progress, not command output. Keep it on stdout in
|
|
20
|
+
* a terminal, but use stderr when stdout is piped so consumers such as `tail`
|
|
21
|
+
* cannot hold the only copy of the live URL until the server exits.
|
|
22
|
+
*/
|
|
23
|
+
declare function announceReviewReady(info: ReviewReadyInfo, stdout?: CliWritable, stderr?: CliWritable): void;
|
|
9
24
|
interface Args {
|
|
10
25
|
command: string;
|
|
11
26
|
manifest: string;
|
|
@@ -76,4 +91,4 @@ declare const COMMANDS: readonly ["init", "plan", "doctor", "gen", "submit", "po
|
|
|
76
91
|
*/
|
|
77
92
|
declare function parseArgs(argv: string[]): Args;
|
|
78
93
|
|
|
79
|
-
export { COMMANDS, parseArgs };
|
|
94
|
+
export { COMMANDS, announceReviewReady, parseArgs };
|
package/dist/cli.js
CHANGED
|
@@ -1550,7 +1550,9 @@ var QualityProfileSchema = z2.object({
|
|
|
1550
1550
|
/** Optional alpha requirement for isolated assets. */
|
|
1551
1551
|
minTransparency: z2.number().min(0).max(1).optional(),
|
|
1552
1552
|
/** Pixel Art Fixer revision written into the quality record. */
|
|
1553
|
-
fixerRevision: z2.string().min(1).optional()
|
|
1553
|
+
fixerRevision: z2.string().min(1).optional(),
|
|
1554
|
+
/** Python executable containing Pixel Art Fixer, relative to the manifest unless absolute. */
|
|
1555
|
+
fixerPython: z2.string().min(1).optional()
|
|
1554
1556
|
}).strict().refine(
|
|
1555
1557
|
(profile) => new Set(profile.palette.map((color) => color.replace(/^#/, "").toLowerCase())).size === profile.palette.length,
|
|
1556
1558
|
{ message: "quality palette colors must be unique", path: ["palette"] }
|
|
@@ -1804,6 +1806,18 @@ var LockEntrySchema = z2.object({
|
|
|
1804
1806
|
maskSha256: z2.string().regex(/^[0-9a-f]{64}$/).optional(),
|
|
1805
1807
|
strength: z2.number().min(0).max(1).optional()
|
|
1806
1808
|
}).strict().nullable().default(null),
|
|
1809
|
+
/**
|
|
1810
|
+
* Output hashes owned by the previous generation while its replacement is
|
|
1811
|
+
* pending. They authorize replacing only unchanged PixelKiln-owned files.
|
|
1812
|
+
*/
|
|
1813
|
+
supersededOutputs: z2.array(
|
|
1814
|
+
z2.object({
|
|
1815
|
+
path: z2.string(),
|
|
1816
|
+
sha256: z2.string(),
|
|
1817
|
+
role: z2.string().optional(),
|
|
1818
|
+
mediaType: MediaTypeSchema.optional()
|
|
1819
|
+
})
|
|
1820
|
+
).optional(),
|
|
1807
1821
|
/** Set at submit time, before the request is awaited, so a crash is recoverable. */
|
|
1808
1822
|
jobId: z2.string().nullable().default(null),
|
|
1809
1823
|
/** For `1dir`: the multi-candidate parent object awaiting selection. */
|
|
@@ -3273,7 +3287,8 @@ async function resolveSpecs(loaded, filter) {
|
|
|
3273
3287
|
palette: style.quality.palette,
|
|
3274
3288
|
minGridConfidence: style.quality.minGridConfidence,
|
|
3275
3289
|
...style.quality.minTransparency == null ? {} : { minTransparency: style.quality.minTransparency },
|
|
3276
|
-
...style.quality.fixerRevision ? { fixerRevision: style.quality.fixerRevision } : {}
|
|
3290
|
+
...style.quality.fixerRevision ? { fixerRevision: style.quality.fixerRevision } : {},
|
|
3291
|
+
...style.quality.fixerPython ? { fixerPython: path4.resolve(root, style.quality.fixerPython) } : {}
|
|
3277
3292
|
}
|
|
3278
3293
|
} : {},
|
|
3279
3294
|
tags,
|
|
@@ -5075,7 +5090,9 @@ async function refineQualityProfiles(specs, lock, options = {}) {
|
|
|
5075
5090
|
minGridConfidence: spec.quality.minGridConfidence,
|
|
5076
5091
|
...spec.quality.minTransparency == null ? {} : { minTransparency: spec.quality.minTransparency },
|
|
5077
5092
|
fixerRevision: spec.quality.fixerRevision ?? PIXEL_ART_FIXER_REVISION,
|
|
5078
|
-
|
|
5093
|
+
// A one-off CLI/library override wins; otherwise each style can carry
|
|
5094
|
+
// the stable project-local interpreter that provides its pinned fixer.
|
|
5095
|
+
fixerPython: options.fixerPython ?? spec.quality.fixerPython,
|
|
5079
5096
|
fixerCommand: options.fixerCommand,
|
|
5080
5097
|
fixerArgsPrefix: options.fixerArgsPrefix,
|
|
5081
5098
|
force: options.force
|
|
@@ -5199,6 +5216,29 @@ async function anyOutputModified(entry, spec) {
|
|
|
5199
5216
|
}
|
|
5200
5217
|
return false;
|
|
5201
5218
|
}
|
|
5219
|
+
function resumeCommandForStatus(status) {
|
|
5220
|
+
if (status === "pending" || status === "processing") return "poll";
|
|
5221
|
+
if (status === "review") return "pick";
|
|
5222
|
+
if (status === "selected" || status === "download-failed") return "fetch";
|
|
5223
|
+
return null;
|
|
5224
|
+
}
|
|
5225
|
+
function resumeActions(specs, lock) {
|
|
5226
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
5227
|
+
for (const spec of specs) {
|
|
5228
|
+
const key = lockKey(spec.styleId, spec.assetId);
|
|
5229
|
+
const entry = lock.entries[key];
|
|
5230
|
+
if (!entry || entry.specHash !== spec.specHash) continue;
|
|
5231
|
+
const command = resumeCommandForStatus(entry.status);
|
|
5232
|
+
if (!command) continue;
|
|
5233
|
+
if (command === "poll" && !entry.jobId) continue;
|
|
5234
|
+
if (command === "pick" && !entry.reviewObjectId) continue;
|
|
5235
|
+
grouped.set(command, [...grouped.get(command) ?? [], key]);
|
|
5236
|
+
}
|
|
5237
|
+
return ["poll", "pick", "fetch"].flatMap((command) => {
|
|
5238
|
+
const keys = grouped.get(command);
|
|
5239
|
+
return keys?.length ? [{ command, keys }] : [];
|
|
5240
|
+
});
|
|
5241
|
+
}
|
|
5202
5242
|
async function buildPlan(specs, lock, opts = {}) {
|
|
5203
5243
|
const items = [];
|
|
5204
5244
|
for (const spec of specs) {
|
|
@@ -5234,10 +5274,20 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
5234
5274
|
reason = "prompt, size, or style changed";
|
|
5235
5275
|
} else if (entry.status === "download-failed") {
|
|
5236
5276
|
state = "recoverable";
|
|
5237
|
-
|
|
5277
|
+
const force = entry.error?.includes("pass --force") ? " --force" : "";
|
|
5278
|
+
reason = `${entry.error ?? "download failed"}; run pixelkiln fetch${force} (no generation cost)`;
|
|
5238
5279
|
} else if (entry.status === "failed") {
|
|
5239
5280
|
state = "failed";
|
|
5240
5281
|
reason = entry.error ?? "previous attempt failed";
|
|
5282
|
+
} else if (entry.status === "selected") {
|
|
5283
|
+
state = "recoverable";
|
|
5284
|
+
reason = "provider output is selected; run pixelkiln fetch (no generation cost)";
|
|
5285
|
+
} else if (entry.status === "pending" || entry.status === "processing") {
|
|
5286
|
+
state = "in-flight";
|
|
5287
|
+
reason = entry.jobId ? "awaiting processing; run pixelkiln poll" : "submission state has no job id; run pixelkiln doctor before retrying";
|
|
5288
|
+
} else if (entry.status === "review") {
|
|
5289
|
+
state = "in-flight";
|
|
5290
|
+
reason = entry.reviewObjectId ? "awaiting review; run pixelkiln pick" : "review state has no review object id; run pixelkiln doctor";
|
|
5241
5291
|
} else if (entry.status !== "downloaded") {
|
|
5242
5292
|
state = "in-flight";
|
|
5243
5293
|
reason = `awaiting ${entry.status}`;
|
|
@@ -5374,6 +5424,8 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
5374
5424
|
const since = Date.now() - lastSubmitAt;
|
|
5375
5425
|
if (since < spacing) await sleep2(spacing - since);
|
|
5376
5426
|
await requireRevisionReady(spec, lock);
|
|
5427
|
+
const previousEntry = lock.entries[key];
|
|
5428
|
+
const supersededOutputs = previousEntry?.outputs.length ? previousEntry.outputs : previousEntry?.supersededOutputs ?? [];
|
|
5377
5429
|
upsert(lock, key, {
|
|
5378
5430
|
styleId: spec.styleId,
|
|
5379
5431
|
assetId: spec.assetId,
|
|
@@ -5397,6 +5449,9 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
5397
5449
|
candidateIndex: null,
|
|
5398
5450
|
error: null,
|
|
5399
5451
|
outputs: [],
|
|
5452
|
+
// Keep the old ownership proof while new bytes are pending. Fetch may
|
|
5453
|
+
// replace that file only while its hash still matches this record.
|
|
5454
|
+
supersededOutputs,
|
|
5400
5455
|
providerMetadata: {},
|
|
5401
5456
|
sourceUrl: null,
|
|
5402
5457
|
sourceUrls: [],
|
|
@@ -5607,22 +5662,37 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
5607
5662
|
);
|
|
5608
5663
|
const target = recorded ? resolveOutputPath(recorded.path, spec.root) : expectedOutputPath(spec, source.role, index, sources.length, source.mediaType);
|
|
5609
5664
|
if (existsSync9(target)) {
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5665
|
+
const currentHash = await sha256File(target);
|
|
5666
|
+
if (recorded && currentHash === recorded.sha256) {
|
|
5667
|
+
if (cacheDir) {
|
|
5668
|
+
await cacheMedia(
|
|
5669
|
+
cacheDir,
|
|
5670
|
+
await readFile7(target),
|
|
5671
|
+
recorded.mediaType ?? MediaType.PNG,
|
|
5672
|
+
recorded.sha256
|
|
5673
|
+
);
|
|
5674
|
+
}
|
|
5675
|
+
outputs.push({ ...recorded, path: portableOutputPath(target, spec.root) });
|
|
5676
|
+
continue;
|
|
5615
5677
|
}
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
|
|
5678
|
+
const superseded = (entry.supersededOutputs ?? []).find(
|
|
5679
|
+
(output, oldIndex, all) => currentOutputPath(output, spec, oldIndex, all.length) === target
|
|
5680
|
+
);
|
|
5681
|
+
if (!opts.force) {
|
|
5682
|
+
if (!recorded && superseded && currentHash === superseded.sha256) {
|
|
5683
|
+
} else if (recorded || superseded) {
|
|
5684
|
+
throw new Error(
|
|
5685
|
+
`refusing to overwrite modified output ${target}; pass --force to replace it`
|
|
5686
|
+
);
|
|
5687
|
+
} else {
|
|
5688
|
+
throw new Error(
|
|
5689
|
+
`refusing to overwrite untracked output ${target}; pass --force to replace it`
|
|
5690
|
+
);
|
|
5691
|
+
}
|
|
5623
5692
|
}
|
|
5624
|
-
|
|
5625
|
-
|
|
5693
|
+
log2(
|
|
5694
|
+
` replace ${path14.relative(process.cwd(), target)}` + (opts.force ? " (--force)" : " (previous tracked generation)")
|
|
5695
|
+
);
|
|
5626
5696
|
}
|
|
5627
5697
|
const expectedMediaType = source.mediaType ?? recorded?.mediaType ?? MediaType.PNG;
|
|
5628
5698
|
let buf = recorded && cacheDir ? await readCachedMedia(cacheDir, recorded.sha256, expectedMediaType) : null;
|
|
@@ -5670,7 +5740,8 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
5670
5740
|
sourceUrl: persistentSources[0]?.url ?? null,
|
|
5671
5741
|
sourceUrls: persistentSources,
|
|
5672
5742
|
downloadedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5673
|
-
error: null
|
|
5743
|
+
error: null,
|
|
5744
|
+
supersededOutputs: []
|
|
5674
5745
|
});
|
|
5675
5746
|
result.downloaded++;
|
|
5676
5747
|
} catch (err) {
|
|
@@ -5841,6 +5912,14 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
5841
5912
|
unsettled ? "warning" : "ok",
|
|
5842
5913
|
unsettled ? `${unsettled} spec(s) are not current: ` + Object.entries(counts).filter(([, n]) => n > 0).map(([state, n]) => `${n} ${state}`).join(", ") : "every resolved spec is current"
|
|
5843
5914
|
);
|
|
5915
|
+
const resumable = resumeActions(specs, lock);
|
|
5916
|
+
add(
|
|
5917
|
+
"next-actions",
|
|
5918
|
+
resumable.length ? "warning" : "ok",
|
|
5919
|
+
resumable.length ? "resume paid work without regenerating: " + resumable.map(
|
|
5920
|
+
(action) => `pixelkiln ${action.command} (${action.keys.length} asset${action.keys.length === 1 ? "" : "s"})`
|
|
5921
|
+
).join(", ") : "no generation stage is waiting for poll, pick, or fetch"
|
|
5922
|
+
);
|
|
5844
5923
|
const quality = plan.items.flatMap((item) => item.quality ? [item.quality] : []);
|
|
5845
5924
|
const qualityPending = quality.filter((item) => item.state !== "approved");
|
|
5846
5925
|
if (quality.length) {
|
|
@@ -6578,10 +6657,15 @@ async function runPicker(provider, lock, lockPath, opts = {}) {
|
|
|
6578
6657
|
onProgress: log2,
|
|
6579
6658
|
assets: reviewAssets,
|
|
6580
6659
|
onReady: (url) => {
|
|
6581
|
-
|
|
6660
|
+
const info = { url, keys: groups.map((group) => group.key) };
|
|
6661
|
+
if (opts.onReady) {
|
|
6662
|
+
opts.onReady(info);
|
|
6663
|
+
} else {
|
|
6664
|
+
log2(`
|
|
6582
6665
|
${groups.length} asset(s) awaiting selection: ${url}`);
|
|
6583
|
-
|
|
6666
|
+
log2(` (leave this running; it exits once you apply)
|
|
6584
6667
|
`);
|
|
6668
|
+
}
|
|
6585
6669
|
},
|
|
6586
6670
|
handleApply: async (body) => {
|
|
6587
6671
|
const { selections } = body;
|
|
@@ -8543,6 +8627,17 @@ async function checkQualityBaseline(baselinePath) {
|
|
|
8543
8627
|
|
|
8544
8628
|
// src/cli.ts
|
|
8545
8629
|
var log = (msg = "") => console.log(msg);
|
|
8630
|
+
function announceReviewReady(info, stdout = process.stdout, stderr = process.stderr) {
|
|
8631
|
+
const stream = stdout.isTTY ? stdout : stderr;
|
|
8632
|
+
const count = info.keys.length;
|
|
8633
|
+
stream.write(
|
|
8634
|
+
`
|
|
8635
|
+
${count} asset${count === 1 ? "" : "s"} awaiting selection: ${info.url}
|
|
8636
|
+
(leave this running; it exits once you apply)
|
|
8637
|
+
|
|
8638
|
+
`
|
|
8639
|
+
);
|
|
8640
|
+
}
|
|
8546
8641
|
async function provenanceFile(id, file) {
|
|
8547
8642
|
const absolute = path24.resolve(file);
|
|
8548
8643
|
return {
|
|
@@ -8900,7 +8995,7 @@ Options
|
|
|
8900
8995
|
--max-colors <n> audit: maximum distinct opaque colors
|
|
8901
8996
|
--sigma <n> audit: relative outlier cutoff (default: 1.5)
|
|
8902
8997
|
--palette <hexes> refine: final comma-separated #rrggbb colors (repeatable)
|
|
8903
|
-
--fixer-python <path> refine: Python with Pixel Art Fixer installed
|
|
8998
|
+
--fixer-python <path> refine: override Python with Pixel Art Fixer installed
|
|
8904
8999
|
--fixer-revision <sha> refine: Pixel Art Fixer revision to record
|
|
8905
9000
|
--min-grid-confidence <level> refine: high (default), medium, or low
|
|
8906
9001
|
--reviewer <name> refine approve: human reviewer recorded in provenance
|
|
@@ -8914,7 +9009,7 @@ Options
|
|
|
8914
9009
|
--budget <n|provider=n> Refuse to exceed one provider ceiling; repeat keyed budgets
|
|
8915
9010
|
for a mixed-provider run
|
|
8916
9011
|
--provider <id> Choose the account for balance/adopt/salvage/purge in a mixed manifest
|
|
8917
|
-
--force Regenerate
|
|
9012
|
+
--force Regenerate, replace a fetch destination, or rebuild managed output
|
|
8918
9013
|
--dry-run Never spend; doctor also skips provider connectivity
|
|
8919
9014
|
--all salvage --dry-run: list every unclaimed object, not just the first 30
|
|
8920
9015
|
--json Machine-readable output where supported, including quality checks
|
|
@@ -8997,6 +9092,17 @@ function printPlan(plan) {
|
|
|
8997
9092
|
}
|
|
8998
9093
|
}
|
|
8999
9094
|
}
|
|
9095
|
+
function printResumeActions(specs, lock) {
|
|
9096
|
+
const actions = resumeActions(specs, lock);
|
|
9097
|
+
for (const action of actions) {
|
|
9098
|
+
const shown = action.keys.slice(0, 3).join(", ");
|
|
9099
|
+
const more = action.keys.length > 3 ? `, +${action.keys.length - 3} more` : "";
|
|
9100
|
+
log(
|
|
9101
|
+
` next: pixelkiln ${action.command} \u2014 ${action.keys.length} asset${action.keys.length === 1 ? "" : "s"} (${shown}${more})`
|
|
9102
|
+
);
|
|
9103
|
+
}
|
|
9104
|
+
return actions.length;
|
|
9105
|
+
}
|
|
9000
9106
|
function manifestProviderIds(manifest) {
|
|
9001
9107
|
return [...new Set(
|
|
9002
9108
|
Object.values(manifest.styles).map((style) => style.provider ?? manifest.provider)
|
|
@@ -10395,7 +10501,10 @@ async function main() {
|
|
|
10395
10501
|
${total.completed} ready \xB7 ${total.review} awaiting selection \xB7 ${total.failed} failed`
|
|
10396
10502
|
);
|
|
10397
10503
|
if (total.failed || total.stillRunning) process.exitCode = 1;
|
|
10398
|
-
if (args.command === "poll")
|
|
10504
|
+
if (args.command === "poll") {
|
|
10505
|
+
printResumeActions(specs, lock);
|
|
10506
|
+
return;
|
|
10507
|
+
}
|
|
10399
10508
|
}
|
|
10400
10509
|
if (args.command === "pick" || args.command === "gen") {
|
|
10401
10510
|
const total = { selected: 0, skipped: 0 };
|
|
@@ -10404,6 +10513,7 @@ async function main() {
|
|
|
10404
10513
|
port: args.port,
|
|
10405
10514
|
open: !args.noOpen,
|
|
10406
10515
|
onProgress: log,
|
|
10516
|
+
onReady: announceReviewReady,
|
|
10407
10517
|
keys: providerSpecsForRun.map((spec) => lockKey(spec.styleId, spec.assetId)),
|
|
10408
10518
|
specs: providerSpecsForRun
|
|
10409
10519
|
});
|
|
@@ -10417,7 +10527,10 @@ async function main() {
|
|
|
10417
10527
|
selected ${total.selected}, left in review ${total.skipped}`);
|
|
10418
10528
|
}
|
|
10419
10529
|
if (args.command === "gen" && total.skipped) process.exitCode = 1;
|
|
10420
|
-
if (args.command === "pick")
|
|
10530
|
+
if (args.command === "pick") {
|
|
10531
|
+
printResumeActions(specs, lock);
|
|
10532
|
+
return;
|
|
10533
|
+
}
|
|
10421
10534
|
}
|
|
10422
10535
|
if (args.command === "fetch" || args.command === "restore" || args.command === "gen") {
|
|
10423
10536
|
log(`
|
|
@@ -10427,7 +10540,8 @@ async function main() {
|
|
|
10427
10540
|
const groupProvider = providerFor(providerId);
|
|
10428
10541
|
const res = await fetchAssets(groupProvider, providerSpecsForRun, lock, args.lock, {
|
|
10429
10542
|
onProgress: log,
|
|
10430
|
-
repair: args.command === "restore"
|
|
10543
|
+
repair: args.command === "restore",
|
|
10544
|
+
force: args.force
|
|
10431
10545
|
});
|
|
10432
10546
|
total.downloaded += res.downloaded;
|
|
10433
10547
|
total.skipped += res.skipped;
|
|
@@ -10446,10 +10560,22 @@ async function main() {
|
|
|
10446
10560
|
if (args.tag) log(` tagged ${total.tagged} object(s) upstream`);
|
|
10447
10561
|
await saveLock(args.lock, lock);
|
|
10448
10562
|
log(` lockfile written: ${args.lock}`);
|
|
10563
|
+
const resumeActionCount = printResumeActions(specs, lock);
|
|
10449
10564
|
if (args.command === "gen" && specs.some((spec) => spec.quality) && !total.failed) {
|
|
10450
|
-
|
|
10565
|
+
const quality = (await Promise.all(
|
|
10566
|
+
specs.filter((spec) => spec.quality).map((spec) => inspectQualityProfile(spec, lock))
|
|
10567
|
+
)).filter((item) => item !== null);
|
|
10568
|
+
const blocked = quality.filter((item) => item.state === "blocked");
|
|
10569
|
+
if (blocked.length) {
|
|
10570
|
+
log(
|
|
10571
|
+
`
|
|
10572
|
+
${blocked.length} quality source${blocked.length === 1 ? " is" : "s are"} not ready; ` + (resumeActionCount ? "finish the resume steps above before refining." : "run `pixelkiln plan` for the blocking reason before refining.")
|
|
10573
|
+
);
|
|
10574
|
+
} else {
|
|
10575
|
+
log(`
|
|
10451
10576
|
Raw provider output is ready. Run \`pixelkiln refine\` to build the configured quality output.`);
|
|
10452
|
-
|
|
10577
|
+
log(` Packaging stays blocked until each refined PNG has a current human approval.`);
|
|
10578
|
+
}
|
|
10453
10579
|
}
|
|
10454
10580
|
return;
|
|
10455
10581
|
}
|
|
@@ -10463,6 +10589,7 @@ main().catch((err) => {
|
|
|
10463
10589
|
});
|
|
10464
10590
|
export {
|
|
10465
10591
|
COMMANDS,
|
|
10592
|
+
announceReviewReady,
|
|
10466
10593
|
parseArgs
|
|
10467
10594
|
};
|
|
10468
10595
|
//# sourceMappingURL=cli.js.map
|