Library API

Load, resolve, and plan by hand

The pieces openProject composes are exported for callers that need one of them alone, for example a manifest with no lockfile yet, or specs resolved against a provider the registry does not know.

import { buildPlan, loadLock, loadManifest, resolveSpecs } from "pixelkiln"

const loaded = await loadManifest("pixelkiln.manifest.json")
const specs = await resolveSpecs(loaded)
const lock = await loadLock("pixelkiln.lock.json")
const plan = await buildPlan(specs, lock)

loadManifest resolves style inheritance before returning. Callers receive ordinary, fully validated styles with defaults applied; the hand-authored extends marker is not present in loaded.manifest. Parent changes therefore flow into child specs without extra work in library integrations.

Planning performs no provider calls and spends nothing. resolveSpecs uses each style's provider or the manifest default, then lets that offline adapter's supports() and estimate() methods determine cost unit and candidate count. Passing options.provider deliberately overrides that routing for custom orchestration and tests. A provider may also resolve local files before hashing. The ComfyUI adapter uses that hook to parse and hash a workflow JSON file without contacting the server. Provider.resolveInputs does the same for per-asset provider values: it can keep an absolute file path in the runtime spec while returning a content-only identity for hashing and provenance. A resolved spec has the fully inherited style and asset settings plus its effective provider and deterministic spec hash. Optional quality settings resolve separately and do not affect provider identity or cost. Revision settings resolve into spec.revision, including the nested parent spec, absolute input paths for I/O, content hashes, measured dimensions, mode, and optional strength. Paths are excluded from the hash; input bytes are not.

plan.groups is the authoritative cost view. Each group contains provider, costUnit, cost, candidates, and its actionable items. For compatibility, plan.cost and plan.costUnit retain the single-provider projection; both are null when a plan spans providers or units.

resumeActions groups current paid-work states into poll, pick, and fetch. It ignores stale specs and entries missing the remote id needed by their stage. It also excludes incomplete multi-request checkpoints; those remain actionable in buildPlan and must return through submit.

For custom orchestration, inspect the dependency gate directly:

import { inspectRevisionReadiness, requireRevisionReady } from "pixelkiln"

const readiness = await inspectRevisionReadiness(spec, lock)
if (readiness && !readiness.ready) console.error(readiness.reason)

// Run again at the boundary where provider work would begin.
await requireRevisionReady(spec, lock)

buildPlan already does the first check and reports an unsafe child as blocked. submit does the second check automatically. Providers advertise accepted modes with optional supportsRevision(mode). Omission means no revision support and makes resolution fail offline.