Commit graph

66 commits

Author SHA1 Message Date
f883d94290
chore: bump openai SDK minimum to 2.25.0
All checks were successful
Continuous Integration / Build Package (push) Successful in 1m50s
Continuous Integration / Lint, Check & Test (push) Successful in 1m59s
2026-03-05 21:21:41 +01:00
47b5f899e6
fix: use list instead of tuple in extra_params for YAML safety
PyYAML serializes tuples as !!python/tuple tags which safe_load
rejects, corrupting the state file.
2026-03-05 21:21:33 +01:00
d90db2933e
fix: wrap image bytes in BytesIO for gpt-image edit endpoint
The OpenAI SDK's legacy multipart path only accepts dall-e-2 when
raw bytes are passed. Wrapping in io.BytesIO with a name attribute
routes through the newer path that supports gpt-image-* models.

Also removes output_format from the edit call as that endpoint
does not support it.
2026-03-05 21:21:26 +01:00
770f408dad
feat: ensure image output format matches file extension
All checks were successful
Continuous Integration / Build Package (push) Successful in 30s
Continuous Integration / Lint, Check & Test (push) Successful in 45s
- Add hokusai/image.py with Pillow-based format detection and conversion
- Pass output_format to OpenAI gpt-image models and BFL API
- Convert mismatched images after provider writes (fallback for all providers)
- Handle RGBA-to-RGB flattening for JPEG targets
- Add Pillow dependency
2026-02-21 19:04:33 +01:00
d8e0ed561d
feat: clean up stale targets removed from config on next build
All checks were successful
Continuous Integration / Build Package (push) Successful in 28s
Continuous Integration / Lint, Check & Test (push) Successful in 47s
When a target is present in the state file but no longer in the config,
its output file is deleted (or archived if archive_folder is set) and
its state entry is removed. This runs at the start of every build.
2026-02-21 18:51:39 +01:00
7503672942
feat: add content targets and loop expansion for target templates
All checks were successful
Continuous Integration / Build Package (push) Successful in 25s
Continuous Integration / Lint, Check & Test (push) Successful in 44s
Content targets write literal text to files via 'content:' field,
without requiring an AI provider or API keys. They are not archived
when overwritten.

Loop expansion allows defining 'loops:' at the top level with named
lists of values. Targets with [var] in their name are expanded via
cartesian product. Variables are substituted in all string fields.
Explicit targets override expanded ones. Escaping: \[var] -> [var].
Expansion happens at config load time so the rest of the system
(builder, graph, state) sees only expanded targets.
2026-02-21 18:39:13 +01:00
bb03975ece
feat: add content target type for writing literal text to files
All checks were successful
Continuous Integration / Build Package (push) Successful in 43s
Continuous Integration / Lint, Check & Test (push) Successful in 1m1s
Content targets write a string directly to the output file without
invoking any AI provider. They don't require API keys and are not
archived when overwritten.

Example usage in .hokusai.yaml:
  file.txt:
    content: ABC
2026-02-21 18:14:09 +01:00
fda28b5afa
docs: update README and CLAUDE.md with recent features
All checks were successful
Continuous Integration / Build Package (push) Successful in 2m8s
Continuous Integration / Lint, Check & Test (push) Successful in 2m24s
- Add regenerate command documentation
- Add download target type
- Fix reference_images field (list, not single string)
- Document archive behavior for clean command
- Update module structure to reflect actual files
- Add OpenAI provider documentation
- Update supported models list
- Add OPENAI_API_KEY to environment variables
2026-02-21 11:48:49 +01:00
d76951fe47
feat: add regenerate command to force rebuild of specific targets
The regenerate command accepts one or more target names and forces them
to be rebuilt even if they are up to date. This respects the
archive_folder setting, archiving previous versions before overwriting.
2026-02-21 11:46:07 +01:00
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
24cade558a
feat: add archive_folder support for preserving previous generations
When archive_folder is set in the project config, artifacts are moved to
numbered archive copies (e.g. x.01.jpg, x.02.jpg) instead of being
overwritten or deleted.

- Build command archives existing artifacts before rebuilding dirty targets
- Clean command moves files to archive instead of deleting them
- Subfolder structure is preserved in the archive directory
- State file is always deleted, never archived
2026-02-21 11:36:45 +01:00
9ace38c806
docs: add hokusai-logo 2026-02-21 11:35:42 +01:00
d22b524182
fix: create parent directories for targets in subfolders
Build targets with subfolder paths (e.g. img/file.jpg) failed because
parent directories did not exist. Create them on demand in
_build_single_target before dispatching to providers.

Also clean up empty subdirectories in the clean command after removing
target files.
2026-02-21 11:21:51 +01:00
8f0e49ee6f
test: add nine-backslash escape test for prompt placeholders 2026-02-20 21:20:30 +01:00
3de3614433
feat: add prompt placeholder substitution with {filename} syntax 2026-02-20 21:20:29 +01:00
760eac5a7b
refactor: use ProjectConfig model for init command YAML output 2026-02-20 21:20:27 +01:00
c1ad6e6e3c
feat: add download target type for fetching files from URLs 2026-02-20 21:20:26 +01:00
a4600df4d5
feat: add init command and --project option to build 2026-02-20 20:45:30 +01:00
d772f5dcc5
feat: accept both .hokusai.yaml and .hokusai.yml config suffixes 2026-02-20 20:33:10 +01:00
1f75c72a96
feat: default to build command when no subcommand is given 2026-02-20 20:29:03 +01:00
4def49350e
chore: rename bulkgen to hokusai
All checks were successful
Continuous Integration / Build Package (push) Successful in 35s
Continuous Integration / Lint, Check & Test (push) Successful in 57s
2026-02-20 17:08:12 +01:00
a28cc97aed
docs: add archive_folder and OpenAI provider to README
All checks were successful
Continuous Integration / Build Package (push) Successful in 1m37s
Continuous Integration / Lint, Check & Test (push) Successful in 2m39s
2026-02-20 16:55:53 +01:00
0294252c2e Merge pull request 'chore(deps): update python docker tag to v3.14' (#2) from renovate/python-3.x into main
All checks were successful
Continuous Integration / Build Package (push) Successful in 34s
Continuous Integration / Lint, Check & Test (push) Successful in 40s
2026-02-18 01:04:15 +01:00
5df5b6e158 chore(deps): update python docker tag to v3.14
All checks were successful
Continuous Integration / Build Package (push) Successful in 2m19s
Continuous Integration / Lint, Check & Test (push) Successful in 2m37s
2026-02-18 00:01:51 +00:00
3bfad87dce
fix: pass all reference images to OpenAI images.edit endpoint
All checks were successful
Continuous Integration / Build Package (push) Successful in 29s
Continuous Integration / Lint, Check & Test (push) Successful in 46s
2026-02-15 15:06:58 +01:00
61f30a8bb1
fix: don't pass response_format to gpt-image-* models
All checks were successful
Continuous Integration / Build Package (push) Successful in 31s
Continuous Integration / Lint, Check & Test (push) Successful in 50s
gpt-image-* models return b64_json by default and reject the
response_format parameter with a 400 error. Only pass it for
DALL-E models which default to url.
2026-02-15 14:47:52 +01:00
d1bbd95c1c
fix: handle long/multiline prompts in _resolve_prompt
All checks were successful
Continuous Integration / Build Package (push) Successful in 31s
Continuous Integration / Lint, Check & Test (push) Successful in 51s
Skip the file-existence check when the prompt contains newlines (can't
be a filename) and catch OSError for prompts that exceed the OS path
length limit.
2026-02-15 14:43:46 +01:00
2aec223c5d
feat: add GPT-5 generation models to OpenAI providers
Text: gpt-5, gpt-5-mini, gpt-5-nano (all with vision), o3, o4-mini
(with vision), o3-pro (text only)
Image: gpt-image-1.5, gpt-image-1-mini (both with reference images)
2026-02-15 14:42:43 +01:00
0ecf1f0f9e
refactor: use project-named state file and store prompt/params directly
All checks were successful
Continuous Integration / Build Package (push) Successful in 48s
Continuous Integration / Lint, Check & Test (push) Successful in 1m1s
- State filename now derives from config: cards.bulkgen.yaml produces
  .cards.bulkgen-state.yaml instead of .bulkgen.state.yaml
- Store resolved prompt text and extra params directly in state file
  instead of hashing them, making state files human-readable
- Only file input contents remain hashed (SHA-256)
- Thread project_name through builder and CLI
- Remove hash_string() and _extra_hash() helpers
- Update .gitignore pattern to .*.bulkgen-state.yaml
2026-02-15 13:56:12 +01:00
870023865d
feat: add OpenAI as provider for text and image generation
- Add openai_text.py: text generation via OpenAI chat completions API
  (gpt-4o, gpt-4o-mini, gpt-4.1, gpt-4.1-mini, gpt-4.1-nano, o3-mini)
- Add openai_image.py: image generation via OpenAI images API
  (gpt-image-1 with reference image support, dall-e-3, dall-e-2)
- Refactor builder provider dispatch from TargetType to model-name index
  to support multiple providers per target type
- Fix circular import between config.py and providers/__init__.py
  using TYPE_CHECKING guard
- Fix stale default model assertions in tests
- Add openai>=1.0.0 dependency
2026-02-15 13:48:06 +01:00
d0dac5b1bf
refactor: move model definitions into providers and extract resolve module
Some checks failed
Continuous Integration / Build Package (push) Successful in 30s
Continuous Integration / Lint, Check & Test (push) Failing after 38s
- Rename ImageProvider to BlackForestProvider, TextProvider to MistralProvider
- Add get_provided_models() abstract method to Provider base class
- Move model lists from models.py into each provider's get_provided_models()
- Add providers/registry.py to aggregate models from all providers
- Extract infer_required_capabilities and resolve_model from config.py to resolve.py
- Update tests to use new names and import paths
2026-02-15 11:03:57 +01:00
dc6a75f5c4
chore: slim down model capabilities
Some checks failed
Continuous Integration / Build Package (push) Successful in 32s
Continuous Integration / Lint, Check & Test (push) Failing after 42s
2026-02-15 10:30:06 +01:00
e7270a118d
feat: replace infer_target_type with capability-based model selection and validation 2026-02-15 10:30:05 +01:00
d15444bdb0
refactor: pass ModelInfo instead of model name string through provider interface 2026-02-15 10:30:04 +01:00
8e3ed7010f
refactor: use StrEnum for model capabilities instead of plain strings 2026-02-15 10:30:03 +01:00
6a80cfb78e
refactor: move models.py into providers package 2026-02-15 10:30:02 +01:00
6a7de0d031 chore: Configure Renovate (#1)
Some checks failed
Continuous Integration / Build Package (push) Successful in 1m54s
Continuous Integration / Lint, Check & Test (push) Failing after 1m58s
Co-authored-by: Renovate Bot <renovate@konstantinfickel.de>
Co-committed-by: Renovate Bot <renovate@konstantinfickel.de>
2026-02-15 07:57:18 +01:00
b536ff9d79
feat: add bulkgen models command listing available models with capabilities
Some checks failed
Continuous Integration / Build Package (push) Successful in 37s
Continuous Integration / Lint, Check & Test (push) Failing after 47s
2026-02-14 23:06:17 +01:00
47b53db760
chore: update default models to pixtral-large-latest and flux-2-pro 2026-02-14 21:27:16 +01:00
ee6c411f3c
feat: add click-based colorized output with progress events and build timer
- Add click as explicit dependency (already bundled with typer)
- Replace typer.echo calls with click.echo + click.style for colorized output
- Add BuildEvent enum and ProgressCallback to builder for decoupled progress reporting
- Remove direct typer dependency from builder module
- Show per-target status with colored labels (skip/ok/fail/...)
- Display elapsed build time in summary
- Colorize graph and clean command output
- Update CLI tests to match new output format
2026-02-14 21:25:38 +01:00
6a9d7efd5d
fix: send images to Mistral as base64 vision chunks instead of placeholders
All checks were successful
Continuous Integration / Build Package (push) Successful in 34s
Continuous Integration / Lint, Check & Test (push) Successful in 53s
The text provider now includes reference_images alongside inputs when
building prompts. Image files are sent as base64 data URLs via
ImageURLChunk for actual multimodal vision support, replacing the
previous [Attached image: ...] placeholder text.
2026-02-14 17:45:39 +01:00
d565329e16
feat: support multiple reference images with model-aware API mapping
All checks were successful
Continuous Integration / Build Package (push) Successful in 31s
Continuous Integration / Lint, Check & Test (push) Successful in 49s
Replace singular reference_image field with reference_images list to
support an arbitrary number of reference images. Map them to the correct
BFL API parameter names based on model family:
- flux-2-*: input_image, input_image_2, ..., input_image_8
- flux-kontext-*: input_image, input_image_2, ..., input_image_4
- flux 1.x: image_prompt (single)

BREAKING CHANGE: reference_image config field renamed to reference_images (list).
2026-02-14 17:19:54 +01:00
b69c38ac13
fix: inline WriteConfig type alias in tests to fix nix flake check
All checks were successful
Continuous Integration / Build Package (push) Successful in 32s
Continuous Integration / Lint, Check & Test (push) Successful in 51s
The tests.conftest import could not be resolved in the nix sandbox
because tests is not a proper package. Define the WriteConfig type
alias directly in test_builder.py and test_graph.py instead.
2026-02-14 16:50:55 +01:00
cf73511876
refactor: replace blackforest package with custom async BFL client
Some checks failed
Continuous Integration / Build Package (push) Successful in 41s
Continuous Integration / Lint, Check & Test (push) Failing after 59s
Implement bulkgen/providers/bfl.py with a fully async httpx-based client
that supports all current and future BFL models (including flux-2-*).
Remove the blackforest dependency and simplify the image provider by
eliminating the asyncio.to_thread wrapper.
2026-02-14 16:44:36 +01:00
fd09d127f2
docs: add home-manager installation instructions to README
Some checks failed
Continuous Integration / Build Package (push) Successful in 35s
Continuous Integration / Lint, Check & Test (push) Failing after 55s
2026-02-14 11:19:15 +01:00
eef9712924
test: add integration tests for all modules
Some checks failed
Continuous Integration / Build Package (push) Successful in 34s
Continuous Integration / Lint, Check & Test (push) Failing after 48s
- Add pytest-asyncio dev dependency and configure asyncio_mode=auto
- Add filterwarnings to suppress third-party PydanticDeprecatedSince20
- Add conftest.py with shared fixtures (project_dir, write_config, etc.)
- Add test_config.py: YAML loading, target type inference, model resolution
- Add test_graph.py: DAG construction, cycle detection, build ordering
- Add test_state.py: hash functions, state persistence, dirty checking
- Add test_builder.py: full build pipeline with FakeProvider, incremental
  builds, selective builds, error isolation, dependency cascading
- Add test_providers.py: ImageProvider and TextProvider with mocked clients
- Add test_cli.py: build/clean/graph commands via typer CliRunner
- All 94 tests pass with 0 basedpyright warnings
2026-02-14 11:07:36 +01:00
452b3c4eb0
docs: add MIT license
All checks were successful
Continuous Integration / Build Package (push) Successful in 2m7s
Continuous Integration / Lint, Check & Test (push) Successful in 2m16s
2026-02-14 10:48:13 +01:00
a662d69559
ci: add forgejo pipeline with nix flake check and build 2026-02-14 10:45:28 +01:00
08952eb70f
feat: add git-hooks.nix pre-commit checks to flake
Add cachix/git-hooks.nix input and wire basedpyright, ruff,
ruff-format, and commitizen hooks into flake checks and devShell.
The basedpyright hook runs against a Nix-built venv so imports
resolve correctly in the sandbox.
2026-02-14 10:42:33 +01:00
a32e0f1b3e
feat: add home-manager module for programs.bulkgen.enable
Expose homeManagerModules.bulkgen and homeManagerModules.default
from the flake. The module provides programs.bulkgen.enable and
programs.bulkgen.package options, adding bulkgen to home.packages
when enabled.
2026-02-14 10:33:33 +01:00