Skip to content

Repository files navigation

Creatifact

Build, version, and distribute AI generation workflows as portable OCI artifacts.

Creatifact turns model calls into durable artifacts. It gives agents and CI a stable CLI and JSON interface for generating text, images, and video, then packages the recipe, inputs, outputs, and provenance into content-addressed OCI artifacts. Push them to any OCI-compatible registry. No Docker daemon is required.

flowchart LR
  A[Agent or CI] -->|CLI or JSON| C[Creatifact]
  C -->|generate or understand| P[Model providers]
  P -->|text, images, video| C
  C -->|recipe + result + provenance| S[OCI store]
  S <--> R[OCI registry]
Loading

Creatifact is useful when a generated asset must outlive the model call that created it:

  • Durable results — download expiring provider URLs into self-contained packages.
  • Traceable provenance — retain effective prompts, model settings, usage, timestamps, and source references.
  • Reusable workflows — package a generation recipe once and run it with new inputs anywhere.
  • Cost-aware orchestration — execute DAG stages concurrently and reuse stages whose resolved inputs have not changed.
  • Provider independence — use one task-oriented contract across built-in and third-party providers.

Install

Creatifact requires Node.js 20 or Node.js 22+.

npm install -g creatifact

Or run it without installing:

npx creatifact --version

Quick start

Set credentials for a built-in provider and choose it as the default:

export ZHIPU_API_KEY="..."
creatifact config set defaults.run.provider zhipu

Generate an image and store it as an OCI package:

creatifact run text2image "a paper crane in the rain" \
  --tag demo/crane:v1

The command returns one machine-readable JSON document on stdout:

{"ok":true,"kind":"run","data":{"task":"text2image","provider":"zhipu","model":"cogview-4","capability":"image.generate","artifacts":[{"url":"https://..."}],"tag":"demo/crane:v1","outputDir":"...","digest":"sha256:..."}}

Inspect the local store:

creatifact package list

To publish the same package, give it a registry-qualified tag and push it:

creatifact tag demo/crane:v1 ghcr.io/acme/crane:v1
creatifact auth login ghcr.io
creatifact push ghcr.io/acme/crane:v1

Another machine or agent can retrieve the exact package:

creatifact pull ghcr.io/acme/crane:v1
creatifact package list

Core workflows

Generate directly

Creatifact exposes model capabilities as tasks instead of provider-specific API shapes. run <task> executes a task; run <ref> executes a packaged recipe (docker-style).

Task Input Output Common options
text2text prompt text --system, --opt
image2text image and question text --input
video2text video and question text --input
text2image prompt image --opt
image2image image and prompt image --image, --opt
text2video prompt video --no-wait, --timeout, --interval
image2video image and prompt video --image
frames2video first frame, last frame, prompt video --first-frame, --last-frame
embed text vectors positional inputs or --input
resume saved video job handle video --timeout, --interval

Examples:

# Select a provider explicitly
creatifact run text2image zhipu "a paper crane"

# Select a provider and model
creatifact run image2video kling/kling-3.0-turbo "animate" \
  --image first.png

# Let the configured default provider choose a suitable model
creatifact run text2text "explain content-addressed storage"

# Submit an asynchronous video job and resume it later
creatifact run text2video ark "a paper crane taking flight" --no-wait
creatifact run resume '{"providerId":"ark","id":"..."}'

Run creatifact models to list providers and creatifact models <provider> to see verified models and their supported tasks. Model discovery does not require credentials.

Generation options use --opt key=value. Values are parsed as JSON when possible, so --opt steps=30 produces a number and --opt watermark=false produces a boolean. Long prompts can live in a file: --prompt-file <path> reads and trims the file's content (mutually exclusive with --prompt and the positional prompt).

Package a reusable recipe

A build manifest can contain a run instruction (dockerfile-style RUN). This packages model defaults, prompts, and input assets into a portable recipe; credentials are never stored in the package.

// creatifact.json
{
  "$schema": "https://github.com/ghraw/unfallenwill/creatifact/main/schemas/creatifact-build.schema.json",
  "assets": "./assets",
  "run": {
    "task": "image2image",
    "provider": "zhipu",
    "model": "cogview-4",
    "prompt": "editorial illustration",
    "images": ["pkg://references/source.png"],
    "options": { "size": "1024x1024" }
  }
}

Build the recipe without calling the provider, then run it with an override:

creatifact build --bake -t ghcr.io/acme/editorial:v1
creatifact push ghcr.io/acme/editorial:v1

creatifact run ghcr.io/acme/editorial:v1 \
  "editorial illustration in red and black"

Long prompts can stay in their own files: set run.promptFile to a path relative to the manifest (mutually exclusive with run.prompt). The file is read and trimmed at load time — the inlined prompt drives fingerprints and the packaged recipe, so built artifacts never reference the file again and prompt edits re-run exactly the stages that consume them.

Without --bake, build executes the run instruction once and packages the result. Running creatifact run <ref> behaves more like running an image: it reads the packaged recipe, applies CLI overrides, calls the provider, and creates a fresh result.

run <ref> accepts a registry reference, a local store tag, or a local OCI layout. Scalar CLI values replace recipe values, arrays replace arrays, and --opt merges individual keys.

Orchestrate a build DAG

Use named stages for multi-step workflows. References such as ${cat.tag} and ${cat.digest} create dependency edges automatically; independent stages run concurrently.

// creatifact.json
{
  "stages": [
    {
      "name": "cat",
      "run": { "task": "text2image", "provider": "zhipu", "prompt": "a cat" }
    },
    {
      "name": "dog",
      "run": { "task": "text2image", "provider": "zhipu", "prompt": "a dog" }
    },
    {
      "name": "gallery",
      "copy": [
        { "from": "${cat.tag}", "paths": ["artifact-1.png"] },
        { "from": "${dog.tag}", "paths": ["artifact-1.png"] }
      ],
      "annotations": {
        "org.example.cat.digest": "${cat.digest}",
        "org.example.dog.digest": "${dog.digest}"
      }
    }
  ]
}
creatifact build -t demo/gallery:v1

Each stage is a mini build with optional from, copy, assets, run, and annotations fields. The last stage is also tagged with the build's -t reference. A stage may reference these outputs from an earlier stage:

  • tag, digest, and outputDir
  • text for text generation and understanding tasks
  • vectors and dimensions for embeddings
  • artifacts[N].url and artifacts[N].base64 for media

The default concurrency is 4. Set defaults.build.concurrency to a positive integer, or to 0 for unlimited concurrency.

Reuse unchanged stages

Before execution, Creatifact fingerprints each stage's resolved inputs: its generation spec, referenced values, source digests, and asset tree. On the next build, unchanged stages are loaded from the content store without another model call.

# See what would execute or be reused; writes nothing and calls no provider
creatifact build -t demo/gallery:v1 --plan

# Ignore previous fingerprints for this run
creatifact build -t demo/gallery:v1 --force

Reuse defaults to "stale". Set defaults.build.reuse to "never" to always execute. Standalone --output builds have no previous store entry to compare and therefore run fully.

Incremental reuse is deliberately a policy, not a claim of deterministic model output. Creatifact does not detect silent provider-side model changes, model default changes, or a mutable remote tag moving to a new digest.

Agent and CI interface

JSON request files

Primary execution and configuration commands also have a JSON form. The command value mirrors the subcommand tree and the remaining fields mirror its arguments. Supported values include run.*, build, push, pull, auth.*, config.*, and models.

{
  "$schema": "https://github.com/ghraw/unfallenwill/creatifact/main/schemas/creatifact-request.schema.json",
  "command": "run.text2image",
  "provider": "zhipu",
  "prompt": "a paper crane",
  "options": { "size": "1024x1024" },
  "tag": "demo/crane:v1"
}
creatifact -f request.json

For run.* requests, trailing CLI flags override file values:

creatifact -f request.json --prompt "a red paper crane" --opt size=2048x2048

A request file represents one command. Multi-step orchestration belongs in a build manifest under stages.

Output contract

Every non-meta command emits exactly one JSON document:

  • Success envelopes go to stdout.
  • Progress and warnings go to stderr.
  • On failure, the last non-empty stderr line is the error envelope and the process exits non-zero.
  • --pretty indents JSON; piped output remains plain JSON.
  • --help, --version, and a bare invocation remain human-readable.
{"ok":false,"kind":"run","error":{"code":"E_PROVIDER","message":"...","details":{"category":"quota","status":429}}}
Code Exit Meaning
E_INTERNAL 1 unclassified internal error
E_USAGE 2 invalid command, arguments, request fields, or local inputs
E_CONFIG 3 invalid or unreadable configuration
E_AUTH 4 missing or invalid credentials
E_NETWORK 5 connection or transport failure
E_PROVIDER 6 provider rejection or failure
E_IO 7 filesystem failure
E_TIMEOUT 8 polling timeout; details include the resumable handle

Packages and registries

Creatifact keeps built, pulled, and generated packages in one shared OCI layout at ~/.creatifact/store. Blobs are deduplicated by digest and tags are movable pointers, similar to a local container image store.

creatifact package list
creatifact package serve --browser
creatifact tag demo/crane:v1 demo/crane:latest
creatifact package rm demo/crane:v1

Removing a tag deletes blobs only when no other tag references them.

package serve starts a local web UI on 127.0.0.1 (random port, override with --port) and prints its URL; with --browser it also opens it in the default browser. The UI is a lazy-loaded waterfall gallery of every package, and per package the run recipe, result metadata, and every file of its layers — media rendered inline. Packages can be deleted right from the page (same semantics as package rm: shared blobs survive). The command prints the JSON envelope (kind: package.serve, carrying the URL) on stdout and runs until Ctrl-C. The UI itself is a Svelte app built into the CLI by npm run build (npm run dev:ui develops it against a running package serve --port 8765 instance).

Registry commands operate on the shared store by default:

creatifact auth login registry.example.com
creatifact push registry.example.com/team/crane:v1
creatifact pull registry.example.com/team/crane:v1

Use --output <dir> to export a standalone OCI layout and --layout <dir> to push one. Registry credentials resolve in this order:

  1. A complete CLI username/password pair
  2. Credentials saved by creatifact auth login
  3. Anonymous access

Use --password-stdin instead of putting secrets in shell history. Saved credentials use the Docker-compatible auths shape in the Creatifact config. --plain-http enables HTTP for a command; loopback registries use HTTP by default, and auths.<registry>.insecure persists that choice per registry.

Bare references use defaults.registry, which defaults to localhost:5000.

Build manifest reference

creatifact build reads creatifact.json from the working directory by default; -f <path> points at any other manifest. A single-package manifest supports these fields:

Field Description
annotations OCI manifest annotations
from One or more registry refs or local OCI layouts whose layers are inherited
copy Selected files or subtrees extracted from source packages into new layers
assets Local directory packed as the top layer, relative to the manifest
run Generation recipe or build-time generation instruction (dockerfile-style RUN)
{
  "$schema": "https://github.com/ghraw/unfallenwill/creatifact/main/schemas/creatifact-build.schema.json",
  "annotations": { "org.opencontainers.image.title": "creative-runtime" },
  "from": ["registry.example.com/base/assets:v1"],
  "copy": [
    { "from": "registry.example.com/team/fonts:v2", "paths": ["fonts"] }
  ],
  "assets": "./project"
}

Layer order is from → copy → assets. Relative paths are resolved from the manifest directory. Copy operations preserve OCI whiteout semantics.

Manifests and -f request files are parsed as JSONC: // and /* */ comments and trailing commas are accepted, so the examples above work verbatim.

Useful build options:

-t, --tag <ref>        Required output tag
-f, --file <path>      Manifest path
    --dir <path>       Override the manifest's assets directory
-o, --output <dir>     Export a standalone OCI layout
    --annotation k=v   Add or override an annotation
    --plan             Print the dry-run execution plan
    --bake             Package a recipe without executing it
    --force            Disable incremental reuse for this run

For editor completion, keep the $schema property shown above or associate the local schema in VS Code:

{
  "json.schemas": [
    {
      "fileMatch": ["creatifact.json"],
      "url": "./schemas/creatifact-build.schema.json"
    }
  ]
}

Providers and models

Built-in providers:

Provider Credentials
Ark ARK_API_KEY
Kling KLING_API_KEY, or KLING_ACCESS_KEY and KLING_SECRET_KEY
MiniMax MINIMAX_API_KEY
Zhipu ZHIPU_API_KEY or BIGMODEL_API_KEY

Credentials may also be stored under providers.<id> in the config. Whole string environment references are resolved at call time, so this keeps the secret outside the file:

creatifact config set providers.zhipu.apiKey '${ZHIPU_API_KEY}'

Use the model catalog before constructing a command:

creatifact models
creatifact models zhipu
creatifact run frames2video zhipu --list-models

Custom models

Add or override provider models under models.<providerId>. This is useful when a provider releases a model before Creatifact's verified registry is updated.

{
  "models": {
    "minimax": [
      {
        "id": "MiniMax-H4",
        "mode": "v2",
        "capabilities": {
          "video.generate": { "textOnly": false, "firstFrame": true }
        }
      }
    ]
  }
}

Unknown model IDs are appended and known IDs are shallowly overridden. Numeric constraints in model notes are hints; the provider API remains authoritative.

Provider plugins

Declare a third-party provider module under providers.<id>.module:

{
  "providers": {
    "my-provider": {
      "module": "creatifact-my-provider",
      "apiKey": "${MY_PROVIDER_API_KEY}"
    }
  }
}

The module must default-export a factory that returns a Provider. Bare package names, relative paths, absolute paths, and ~/... paths are supported.

import {
  createJsonClient,
  defineProvider,
  type Provider,
} from "creatifact/providers"

interface Settings {
  apiKey?: string
}

export default defineProvider((settings: Settings, env) => {
  const apiKey = settings.apiKey ?? env["MY_PROVIDER_API_KEY"]
  if (!apiKey) throw new Error("missing MY_PROVIDER_API_KEY")

  const client = createJsonClient({
    baseUrl: "https://api.example.com",
    headers: { authorization: `Bearer ${apiKey}` },
  })

  const provider: Provider = {
    id: "my-provider",
    models: [
      {
        id: "my-image-model",
        capabilities: { "image.generate": {} },
        lastVerified: "2026-08",
      },
    ],
    defaultModels: { "image.generate": "my-image-model" },
    imageGenerate: {
      async create(req) {
        const result = await client.post<{ url: string }>("/v1/images", req)
        if (result.isErr()) throw result.error
        return { artifacts: [{ url: result.value.url }] }
      },
    },
  }

  return provider
})

Plugin types and helpers are exported from creatifact/providers. Add creatifact as a development dependency to compile a plugin; the CLI loads the module at runtime.

Configuration

The default config path is ~/.creatifact/config.json. Override its directory with CREATIFACT_CONFIG_DIR or pass --config-dir <dir> to any command.

{
  "defaults": {
    "registry": "ghcr.io",
    "run": { "provider": "zhipu" },
    "build": { "concurrency": 4, "reuse": "stale" }
  },
  "providers": {
    "zhipu": { "apiKey": "${ZHIPU_API_KEY}" }
  }
}

Manage it through the CLI:

creatifact config path
creatifact config list
creatifact config get defaults.run.provider
creatifact config set defaults.run.provider zhipu
creatifact config reset

Secret-looking values are masked by config list and config get. Config writes are atomic, and a corrupt file fails loudly instead of being ignored.

Artifact semantics

A generated package contains the effective generation spec and result metadata. Media artifacts are downloaded into a layer when possible so the package does not depend on an expiring CDN URL; the original URL remains in metadata for provenance. If the URL is already unavailable, Creatifact preserves a URL-only record and emits a warning.

Text and embedding tasks return data.text or data.vectors directly. Pass --tag or --output to package them as text.txt or vectors.json, which can then be consumed through pkg:// references in another recipe.

Creatifact provides immutable bytes, recorded inputs, and traceable lineage. It does not promise that rerunning a stochastic or silently updated model will produce identical bytes.

Development

npm run dev          # Run src/index.ts directly
npm run typecheck    # Strict TypeScript check
npm run gen:schemas  # Regenerate schemas from src/lib/contract/contract.ts
npm run build        # Bundle the CLI
npm test             # Run Vitest once
npm run qa           # Typecheck, build, test, lint, dependency check, smoke test

src/lib/contract/contract.ts is the single source of truth for request and build schemas.

See CONTRIBUTING.md for the full development workflow: the QA gate, commit conventions, architecture rules, and guides for adding providers, commands, and schema changes.

License

MIT

Releases

Packages

Contributors

Languages