Commit graph

24 commits

Author SHA1 Message Date
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
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
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
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
c1ad6e6e3c
feat: add download target type for fetching files from URLs 2026-02-20 21:20:26 +01:00
d772f5dcc5
feat: accept both .hokusai.yaml and .hokusai.yml config suffixes 2026-02-20 20:33:10 +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
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
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
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
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
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