Build-tool for bulk-generating AI images.
Find a file
Konstantin Fickel 612ea0ae9d
refactor: clean up download target state representation
Download targets now store only 'download: <url>' in the state file
instead of using 'prompt' and 'model: __download__' as a workaround.

Also use exclude_defaults=True when serializing state to omit empty
fields like input_hashes: {} and extra_params: {}.
2026-02-21 11:42:19 +01:00
.forgejo/workflows ci: add forgejo pipeline with nix flake check and build 2026-02-14 10:45:28 +01:00
hokusai refactor: clean up download target state representation 2026-02-21 11:42:19 +01:00
nix chore: rename bulkgen to hokusai 2026-02-20 17:08:12 +01:00
tests feat: add archive_folder support for preserving previous generations 2026-02-21 11:36:45 +01:00
.envrc build: set up project for bulkgen 2026-02-13 19:32:47 +01:00
.gitignore chore: rename bulkgen to hokusai 2026-02-20 17:08:12 +01:00
.python-version chore: rename bulkgen to hokusai 2026-02-20 17:08:12 +01:00
CLAUDE.md chore: rename bulkgen to hokusai 2026-02-20 17:08:12 +01:00
devenv.lock build: set up project for bulkgen 2026-02-13 19:32:47 +01:00
devenv.nix refactor: switch to basedpyright, remove pydantic-settings 2026-02-13 20:25:28 +01:00
devenv.yaml build: set up project for bulkgen 2026-02-13 19:32:47 +01:00
flake.lock feat: add git-hooks.nix pre-commit checks to flake 2026-02-14 10:42:33 +01:00
flake.nix chore: rename bulkgen to hokusai 2026-02-20 17:08:12 +01:00
LICENSE.md docs: add MIT license 2026-02-14 10:48:13 +01:00
logo.png docs: add hokusai-logo 2026-02-21 11:35:42 +01:00
main.py chore: rename bulkgen to hokusai 2026-02-20 17:08:12 +01:00
pyproject.toml chore: rename bulkgen to hokusai 2026-02-20 17:08:12 +01:00
pyrightconfig.json refactor: switch to basedpyright, remove pydantic-settings 2026-02-13 20:25:28 +01:00
README.md docs: add hokusai-logo 2026-02-21 11:35:42 +01:00
renovate.json chore: Configure Renovate (#1) 2026-02-15 07:57:18 +01:00
uv.lock chore: rename bulkgen to hokusai 2026-02-20 17:08:12 +01:00

hokusai

A build tool for AI-generated artifacts. Define image and text targets in a YAML config, and hokusai handles dependency resolution, incremental builds, and parallel execution.

Uses Mistral and OpenAI for text generation, and BlackForestLabs (FLUX) and OpenAI for image generation.

The name Hokusai was chosen in honor of Katsushika Hokusai, who produced over 30,000 paintings, sketches, woodblock prints, and images for picture books, many in larger series.

Installation

Requires Python 3.13+.

pip install .

Or with uv:

uv sync

Quick start

  1. Set your API keys:
export MISTRAL_API_KEY="your-key"
export BFL_API_KEY="your-key"
export OPENAI_API_KEY="your-key"
  1. Create a config file (e.g. my-project.hokusai.yaml):
defaults:
  text_model: mistral-large-latest
  image_model: flux-pro

targets:
  hero.png:
    prompt: "A dramatic sunset over mountains, photorealistic"
    width: 1024
    height: 768

  blog-post.md:
    prompt: prompts/write-blog.txt
    inputs:
      - hero.png
      - notes.md
  1. Build:
hokusai build

Config format

The config file must be named <anything>.hokusai.yaml and placed in your project directory. One config file per directory.

Top-level fields

Field Description
defaults Default model names (optional)
archive_folder Directory to move previous outputs into before rebuilding (optional)
targets Map of output filenames to their configuration

Defaults

defaults:
  text_model: mistral-large-latest   # used for .md, .txt targets
  image_model: flux-pro              # used for .png, .jpg, .jpeg, .webp targets

Target fields

Field Type Description
prompt string Inline prompt text, or path to a prompt file
model string Override the default model for this target
inputs list[string] Files this target depends on (other targets or existing files)
reference_image string Image file for image-to-image generation
control_images list[string] Control images (for canny/depth models)
width int Image width in pixels
height int Image height in pixels

Target type is inferred from the file extension:

  • Image: .png, .jpg, .jpeg, .webp
  • Text: .md, .txt

Prompts

Prompts can be inline strings or file references:

targets:
  # Inline prompt
  image.png:
    prompt: "A cat sitting on a windowsill"

  # File reference (reads the file contents as the prompt)
  article.md:
    prompt: prompts/article-prompt.txt

If the prompt value is a path to an existing file, its contents are read. Otherwise the string is used directly.

Dependencies

Targets can depend on other targets or on existing files in the project directory:

targets:
  base.png:
    prompt: "A landscape scene"

  variant.png:
    prompt: "Same scene but in winter"
    reference_image: base.png    # image-to-image, depends on base.png

  summary.md:
    prompt: "Summarize these notes"
    inputs:
      - base.png                 # depends on a generated target
      - research-notes.md        # depends on an existing file

hokusai resolves dependencies automatically. If you build a single target, its transitive dependencies are included.

Archiving previous outputs

Set archive_folder at the top level to preserve previous versions of generated files. When a target is rebuilt, the existing output is moved to the archive folder with an incrementing numeric suffix:

archive_folder: archive

targets:
  hero.png:
    prompt: "A dramatic sunset over mountains"

On each rebuild of hero.png, the previous file is archived as archive/hero.01.png, archive/hero.02.png, etc. The archive directory is created automatically if it doesn't exist.

CLI

hokusai build [target]

Build all targets, or a specific target and its dependencies.

  • Skips targets that are already up to date (incremental builds)
  • Runs independent targets in parallel
  • Continues building if a target fails (dependents of the failed target are skipped)

hokusai clean

Remove all generated target files and the build state file (.hokusai.state.yaml). Input files are preserved.

hokusai graph

Print the dependency graph showing build stages:

Stage 0 (inputs): research-notes.md
Stage 0 (targets): base.png
Stage 1 (targets): variant.png, summary.md
  variant.png <- base.png
  summary.md <- base.png, research-notes.md

Incremental builds

hokusai tracks the state of each build in .hokusai.state.yaml (auto-generated, add to .gitignore). A target is rebuilt when any of these change:

  • Input file contents (SHA-256 hash)
  • Prompt text
  • Model name
  • Extra parameters (width, height, etc.)

Installation with Nix / home-manager

hokusai provides a Nix flake with a home-manager module. Add the flake as an input and enable the module:

# flake.nix
{
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
    home-manager.url = "github:nix-community/home-manager";
    hokusai.url = "github:kfickel/hokusai";  # adjust to your actual repo URL
  };

  outputs = { nixpkgs, home-manager, hokusai, ... }: {
    # ... your existing config, then in homeConfigurations:
    homeConfigurations."user" = home-manager.lib.homeManagerConfiguration {
      # ...
      modules = [
        hokusai.homeManagerModules.hokusai
        {
          programs.hokusai.enable = true;
        }
      ];
    };
  };
}

This places the hokusai binary on your $PATH. To use a different package build (e.g. from a different system or overlay), set programs.hokusai.package.

The flake also exposes:

  • packages.<system>.hokusai — the standalone package, usable without home-manager (e.g. nix run github:kfickel/hokusai)
  • devShells.<system>.default — development shell with all dependencies

Environment variables

Variable Required for
MISTRAL_API_KEY Text targets via Mistral models
BFL_API_KEY Image targets via FLUX models
OPENAI_API_KEY Text and image targets via OpenAI models