zer0-image-generator 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +42 -0
- data/LICENSE +21 -0
- data/README.md +212 -0
- data/lib/zer0-image-generator.rb +15 -0
- data/lib/zer0_image_generator/command.rb +106 -0
- data/lib/zer0_image_generator/preview_generator.py +2435 -0
- data/lib/zer0_image_generator/rasterize-svg.js +65 -0
- data/lib/zer0_image_generator/version.rb +5 -0
- metadata +84 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: e7f4e6c4643293895075982356663b1f49b405838640ccb8455db1b93060ce5a
|
|
4
|
+
data.tar.gz: 8708e0c991d7dd350c18ce958e3a4628393dc923c4594c42eded85b04d3839d9
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 1fffd493ddbf20127ef605568aa4ce7b04ad8861a6c4e579c257dd3c253df924f7cba9272a866c0885e49e662dec568e0d1bc9e92eb5c780bd6c9a686de76db6
|
|
7
|
+
data.tar.gz: 77a9cc97328ac34db10e7285eac3aa6a535ae3fdcbd0b8e481063da402575335c9c260009b1f81fb0adbaeb50177e9d8dc750c1435987b77df7563f864352a6a
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.0] - 2026-07-13
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Initial extraction of the AI preview-image engine from the
|
|
13
|
+
[zer0-mistakes](https://github.com/bamr87/zer0-mistakes) theme
|
|
14
|
+
(`scripts/lib/preview_generator.py`, PR bamr87/zer0-mistakes#296) as a
|
|
15
|
+
standalone, portable project.
|
|
16
|
+
- `zer0-image-generator` Ruby gem: a `jekyll preview-images` command
|
|
17
|
+
(`Jekyll::Command`) that launches the vendored single-file Python engine —
|
|
18
|
+
the Ruby layer is a thin argv translator so gem and standalone usage can
|
|
19
|
+
never drift.
|
|
20
|
+
- Universality knobs (all new, all with zer0-compatible defaults):
|
|
21
|
+
- `collections_dir` — honored from Jekyll's own top-level key, the
|
|
22
|
+
`preview_images:` block, or `--collections-dir` (the theme hardcoded
|
|
23
|
+
`pages/`).
|
|
24
|
+
- `front_matter_key` — read/write any key (`preview` default; `image` for
|
|
25
|
+
jekyll-seo-tag sites) via config or `--front-matter-key`.
|
|
26
|
+
- `authors_file` — per-author style overrides from any YAML path; empty
|
|
27
|
+
disables (was hardcoded `_data/authors.yml`).
|
|
28
|
+
- `collections: auto` — discover collections from Jekyll's `collections:`
|
|
29
|
+
map instead of listing them.
|
|
30
|
+
- Vendored Playwright SVG→PNG rasterizer helper next to the engine (gem
|
|
31
|
+
layout), keeping the rsvg → inkscape → magick → playwright chain intact.
|
|
32
|
+
- Unit suite (98 tests, zero network) plus a standard-layout fixture site
|
|
33
|
+
exercised end-to-end in CI with the free `local` provider.
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
- Default `collections` is now `[posts]` (universal) instead of the theme's
|
|
38
|
+
`[posts, quickstart, docs]` — zer0-mistakes lists its collections
|
|
39
|
+
explicitly, so theme behavior is unchanged when it adopts this engine.
|
|
40
|
+
- `resolve_settings(args, config)` now takes the FULL `_config.yml` dict
|
|
41
|
+
(it extracts the `preview_images:` block itself) so top-level Jekyll keys
|
|
42
|
+
(`collections_dir`, `collections`) can participate in resolution.
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-2026 Amr Abdel
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# zer0-image-generator
|
|
2
|
+
|
|
3
|
+
[](https://github.com/bamr87/zer0-image-generator/actions/workflows/test.yml)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
|
|
6
|
+
AI preview/social images for **any Jekyll site** — Claude directs and reviews,
|
|
7
|
+
an image model renders, and your front matter gets wired up automatically.
|
|
8
|
+
|
|
9
|
+
Extracted from the [zer0-mistakes](https://github.com/bamr87/zer0-mistakes)
|
|
10
|
+
theme's consolidated engine and generalized: every theme-specific assumption
|
|
11
|
+
is now a config knob with zer0-compatible defaults, so the theme and this
|
|
12
|
+
plugin stay **separate but portable**.
|
|
13
|
+
|
|
14
|
+
## How it works
|
|
15
|
+
|
|
16
|
+
Each post/page without a preview goes through a three-stage pipeline:
|
|
17
|
+
|
|
18
|
+
| stage | role | engine / credential |
|
|
19
|
+
| ------- | ----------------------------------------------------------- | -------------------------------------- |
|
|
20
|
+
| analyze | Claude reads the article and writes a vivid art brief | Claude credential chain (see below) |
|
|
21
|
+
| produce | an image model renders the brief | the selected provider |
|
|
22
|
+
| review | Claude inspects the render (vision); one refined regen max | same Claude credential chain |
|
|
23
|
+
|
|
24
|
+
| provider | renderer | credential |
|
|
25
|
+
| ----------- | ------------------------------------------ | ------------------- |
|
|
26
|
+
| **openai** (default) | gpt-image-2 / dall-e-3 (+ `--enhance`) | `OPENAI_API_KEY` |
|
|
27
|
+
| xai | grok-2-image | `XAI_API_KEY` |
|
|
28
|
+
| stability | Stable Diffusion XL | `STABILITY_API_KEY` |
|
|
29
|
+
| gemini | gemini-2.5-flash-image | `GEMINI_API_KEY` |
|
|
30
|
+
| local | deterministic template SVG → PNG | none (CI-safe) |
|
|
31
|
+
|
|
32
|
+
Claude never renders pixels (the Anthropic API has no image endpoint); without
|
|
33
|
+
a Claude credential the analyze/review stages degrade gracefully to a template
|
|
34
|
+
prompt and the renderer still runs.
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
### As a Jekyll plugin (recommended)
|
|
39
|
+
|
|
40
|
+
```ruby
|
|
41
|
+
# Gemfile
|
|
42
|
+
group :jekyll_plugins do
|
|
43
|
+
gem "zer0-image-generator"
|
|
44
|
+
end
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Requires `python3` (3.9+) with PyYAML — the command checks and tells you if
|
|
48
|
+
either is missing:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
python3 -m pip install pyyaml
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Then, from your site root:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
bundle exec jekyll preview-images --list-missing # what needs a banner?
|
|
58
|
+
bundle exec jekyll preview-images --dry-run # what would be generated?
|
|
59
|
+
bundle exec jekyll preview-images # generate them
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Standalone (no Ruby required)
|
|
63
|
+
|
|
64
|
+
The engine is one dependency-light Python file. Copy it anywhere and run it
|
|
65
|
+
from your site root:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
curl -o preview_generator.py \
|
|
69
|
+
https://raw.githubusercontent.com/bamr87/zer0-image-generator/main/lib/zer0_image_generator/preview_generator.py
|
|
70
|
+
python3 preview_generator.py --dry-run
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Configuration
|
|
74
|
+
|
|
75
|
+
Everything lives under `preview_images:` in your `_config.yml`. Shown with
|
|
76
|
+
defaults:
|
|
77
|
+
|
|
78
|
+
```yaml
|
|
79
|
+
preview_images:
|
|
80
|
+
enabled: true
|
|
81
|
+
provider: openai # openai | xai | stability | gemini | local
|
|
82
|
+
model: '' # empty = provider default (gpt-image-2, ...)
|
|
83
|
+
size: 1536x1024
|
|
84
|
+
quality: auto # auto (GPT Image) | standard/hd (DALL-E 3)
|
|
85
|
+
style: "retro pixel art, 8-bit video game aesthetic, vibrant colors"
|
|
86
|
+
style_modifiers: "pixelated, retro gaming style, CRT screen glow effect"
|
|
87
|
+
output_dir: assets/images/previews
|
|
88
|
+
prompt_engine: claude # claude analyzes each article | template
|
|
89
|
+
review_engine: claude # claude vision-reviews each render | none
|
|
90
|
+
# --- portability knobs (the zer0-isms, now configurable) ---
|
|
91
|
+
collections: [posts] # list of collections, or 'auto' to discover
|
|
92
|
+
# them from Jekyll's own `collections:` map
|
|
93
|
+
collections_dir: '' # falls back to Jekyll's top-level collections_dir
|
|
94
|
+
front_matter_key: preview # key to read/write (jekyll-seo-tag sites: image)
|
|
95
|
+
authors_file: _data/authors.yml # per-author style overrides ('' disables)
|
|
96
|
+
assets_prefix: /assets # written paths omit this prefix ...
|
|
97
|
+
auto_prefix: true # ... and it is re-added for existence checks
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Priority per file: author overrides → CLI flags → environment variables →
|
|
101
|
+
`_config.yml` → built-in defaults.
|
|
102
|
+
|
|
103
|
+
### Recipe: standard Jekyll site with jekyll-seo-tag
|
|
104
|
+
|
|
105
|
+
```yaml
|
|
106
|
+
preview_images:
|
|
107
|
+
provider: openai
|
|
108
|
+
collections: auto
|
|
109
|
+
front_matter_key: image # jekyll-seo-tag reads page.image
|
|
110
|
+
assets_prefix: '' # write full site-absolute paths
|
|
111
|
+
authors_file: ''
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Generated front matter: `image: /assets/images/previews/my-post.png` — exactly
|
|
115
|
+
what `jekyll-seo-tag` turns into `og:image`.
|
|
116
|
+
|
|
117
|
+
### Recipe: zer0-mistakes theme
|
|
118
|
+
|
|
119
|
+
```yaml
|
|
120
|
+
preview_images:
|
|
121
|
+
provider: openai
|
|
122
|
+
collections: [posts, docs, quickstart]
|
|
123
|
+
# collections_dir comes from the theme's own `collections_dir: pages`
|
|
124
|
+
# front_matter_key/preview + /assets prefixing are already the defaults
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Per-author style overrides
|
|
128
|
+
|
|
129
|
+
If `authors_file` points at a YAML map, an author's `preview:` block overrides
|
|
130
|
+
`style`, `style_modifiers`, `size`, `quality`, and `model` for their pages:
|
|
131
|
+
|
|
132
|
+
```yaml
|
|
133
|
+
# _data/authors.yml
|
|
134
|
+
cassandra:
|
|
135
|
+
preview:
|
|
136
|
+
style: >-
|
|
137
|
+
noir graphic-novel, high-contrast ink
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Credentials
|
|
141
|
+
|
|
142
|
+
The renderer needs its own key (default: `OPENAI_API_KEY`). Claude
|
|
143
|
+
orchestration accepts any ONE of, in order — it is optional:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
export CLAUDE_CODE_OAUTH_TOKEN="..." # 1. from `claude setup-token` (Pro/Max)
|
|
147
|
+
export ANTHROPIC_AUTH_TOKEN="..." # 2. short-lived bearer token
|
|
148
|
+
export ANTHROPIC_API_KEY="..." # 3. console.anthropic.com
|
|
149
|
+
# 4. nothing — a logged-in `claude` CLI is used automatically
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Keys are read from the environment or a git-ignored `.env` at the site root.
|
|
153
|
+
On a Claude Pro/Max subscription the orchestration costs nothing extra; only
|
|
154
|
+
the renderer bills per image.
|
|
155
|
+
|
|
156
|
+
## CLI reference
|
|
157
|
+
|
|
158
|
+
`jekyll preview-images` exposes the engine's flags (Jekyll claims `-s/-d/-p`
|
|
159
|
+
globally, so use long forms there; the Python CLI keeps all short flags):
|
|
160
|
+
|
|
161
|
+
```text
|
|
162
|
+
--dry-run --verbose --force --list-missing
|
|
163
|
+
-f/--file FILE --collection NAME|all --provider NAME
|
|
164
|
+
--model NAME --output-dir DIR -j/--parallel N
|
|
165
|
+
--style TEXT --prompt-engine claude|template
|
|
166
|
+
--review claude|none --rasterizer auto|rsvg|inkscape|magick|playwright|none
|
|
167
|
+
-e/--enhance --enhance-prompt/-model/-quality/-fidelity/-format
|
|
168
|
+
--collections-dir DIR --front-matter-key KEY --authors-file FILE
|
|
169
|
+
--assets-prefix PREFIX --no-auto-prefix --batch N --log-file FILE
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
The `local` provider rasterizes its SVG via the first available of
|
|
173
|
+
`rsvg-convert` (`brew install librsvg` / `apt install librsvg2-bin`),
|
|
174
|
+
`inkscape`, ImageMagick, or a vendored Playwright helper.
|
|
175
|
+
|
|
176
|
+
## CI usage
|
|
177
|
+
|
|
178
|
+
```yaml
|
|
179
|
+
- name: Generate preview images
|
|
180
|
+
env:
|
|
181
|
+
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
182
|
+
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
183
|
+
run: bundle exec jekyll preview-images
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Generation is script-driven on demand — it is never part of `jekyll build`
|
|
187
|
+
(builds stay fast, deterministic, and secret-free; GitHub Pages safe mode is
|
|
188
|
+
irrelevant to it).
|
|
189
|
+
|
|
190
|
+
## Relationship to zer0-mistakes
|
|
191
|
+
|
|
192
|
+
The [zer0-mistakes](https://github.com/bamr87/zer0-mistakes) theme renders
|
|
193
|
+
`preview:` values with a pure-Liquid include (GitHub-Pages-safe, no plugin
|
|
194
|
+
needed) and currently vendors this engine at `scripts/lib/preview_generator.py`.
|
|
195
|
+
This repo is the portable home: the theme is expected to consume the gem (or
|
|
196
|
+
curl the engine from here) in a follow-up, so there is exactly ONE engine.
|
|
197
|
+
|
|
198
|
+
## Development
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
python3 test/test_preview_generator.py # 98 unit tests, zero network
|
|
202
|
+
python3 -m py_compile lib/zer0_image_generator/preview_generator.py
|
|
203
|
+
gem build zer0-image-generator.gemspec
|
|
204
|
+
cd test/fixture-site && bundle install
|
|
205
|
+
bundle exec jekyll preview-images --dry-run # integration fixture
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Conventional Commits drive releases via release-please. One concern per PR.
|
|
209
|
+
|
|
210
|
+
## License
|
|
211
|
+
|
|
212
|
+
[MIT](LICENSE) © Amr Abdel
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Entry point required by Bundler's :jekyll_plugins group. Registers the
|
|
4
|
+
# `jekyll preview-images` command when Jekyll is present; outside Jekyll the
|
|
5
|
+
# gem is inert (the Python engine can always be run directly:
|
|
6
|
+
# python3 lib/zer0_image_generator/preview_generator.py).
|
|
7
|
+
require_relative "zer0_image_generator/version"
|
|
8
|
+
|
|
9
|
+
begin
|
|
10
|
+
require "jekyll"
|
|
11
|
+
rescue LoadError
|
|
12
|
+
# Standalone/non-Jekyll context — nothing to register.
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
require_relative "zer0_image_generator/command" if defined?(Jekyll::Command)
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Zer0ImageGenerator
|
|
4
|
+
# `jekyll preview-images` — a thin launcher for the vendored single-file
|
|
5
|
+
# Python engine (preview_generator.py, same directory). The Ruby layer only
|
|
6
|
+
# translates Mercenary options back into the engine's argv and execs
|
|
7
|
+
# python3: all behavior (config resolution, credentials, providers, front
|
|
8
|
+
# matter) lives in the engine so the gem and the standalone file can never
|
|
9
|
+
# drift.
|
|
10
|
+
#
|
|
11
|
+
# Jekyll's global switches already claim -s/-d/-p/-v at the program level,
|
|
12
|
+
# so conflicting engine shorts are exposed long-form here; the engine's own
|
|
13
|
+
# CLI keeps the full short surface for direct `python3` use.
|
|
14
|
+
class Command < Jekyll::Command
|
|
15
|
+
ENGINE = File.expand_path("preview_generator.py", __dir__)
|
|
16
|
+
|
|
17
|
+
# [engine flag, mercenary args] — booleans pass bare, values pass through.
|
|
18
|
+
SWITCHES = [
|
|
19
|
+
["--dry-run", [:dry_run, "--dry-run", "Preview what would be generated (no changes)"]],
|
|
20
|
+
["--verbose", [:verbose, "--verbose", "Enable verbose output"]],
|
|
21
|
+
["--file", [:file, "-f", "--file FILE", "Process a specific file only"]],
|
|
22
|
+
["--collection", [:collection, "--collection NAME", "Process one collection, or 'all'"]],
|
|
23
|
+
["--provider", [:provider, "--provider NAME", "Renderer: openai, xai, stability, gemini, local"]],
|
|
24
|
+
["--model", [:model, "--model NAME", "Override the provider's image model"]],
|
|
25
|
+
["--output-dir", [:output_dir, "--output-dir DIR", "Image output directory"]],
|
|
26
|
+
["--force", [:force, "--force", "Regenerate even when a preview exists"]],
|
|
27
|
+
["--list-missing", [:list_missing, "--list-missing", "Only list files with missing previews"]],
|
|
28
|
+
["--parallel", [:parallel, "-j", "--parallel N", "Concurrent workers (default 4)"]],
|
|
29
|
+
["--enhance", [:enhance, "-e", "--enhance", "Enhance existing images (OpenAI images/edits)"]],
|
|
30
|
+
["--enhance-prompt", [:enhance_prompt, "--enhance-prompt TEXT", "Custom enhancement prompt (implies --enhance)"]],
|
|
31
|
+
["--enhance-model", [:enhance_model, "--enhance-model NAME", "Enhancement model (default: gpt-image-2)"]],
|
|
32
|
+
["--enhance-quality", [:enhance_quality, "--enhance-quality Q", "low|medium|high|auto"]],
|
|
33
|
+
["--enhance-fidelity", [:enhance_fidelity, "--enhance-fidelity F", "high|low (implies --enhance)"]],
|
|
34
|
+
["--enhance-format", [:enhance_format, "--enhance-format FMT", "png|jpeg|webp (implies --enhance)"]],
|
|
35
|
+
["--prompt-engine", [:prompt_engine, "--prompt-engine ENGINE", "claude (analyzes the article) or template"]],
|
|
36
|
+
["--review", [:review, "--review ENGINE", "claude (vision-reviews the render) or none"]],
|
|
37
|
+
["--rasterizer", [:rasterizer, "--rasterizer TOOL", "auto|rsvg|inkscape|magick|playwright|none"]],
|
|
38
|
+
["--style", [:style, "--style TEXT", "Override the image style prompt"]],
|
|
39
|
+
["--assets-prefix", [:assets_prefix, "--assets-prefix PREFIX", "Assets prefix for path normalization"]],
|
|
40
|
+
["--no-auto-prefix", [:no_auto_prefix, "--no-auto-prefix", "Disable automatic assets-prefix prepending"]],
|
|
41
|
+
["--batch", [:batch, "--batch N", "Limit number of files processed"]],
|
|
42
|
+
["--log-file", [:log_file, "--log-file FILE", "Also write log output to a file"]],
|
|
43
|
+
["--collections-dir", [:collections_dir, "--collections-dir DIR", "Directory holding _<collection> dirs"]],
|
|
44
|
+
["--front-matter-key", [:front_matter_key, "--front-matter-key KEY", "Front-matter key (default preview; seo-tag sites: image)"]],
|
|
45
|
+
["--authors-file", [:authors_file, "--authors-file FILE", "Author-overrides YAML ('' disables)"]],
|
|
46
|
+
].freeze
|
|
47
|
+
|
|
48
|
+
BOOLEANS = %w[--dry-run --verbose --force --list-missing --enhance --no-auto-prefix].freeze
|
|
49
|
+
|
|
50
|
+
class << self
|
|
51
|
+
def init_with_program(prog)
|
|
52
|
+
prog.command(:"preview-images") do |c|
|
|
53
|
+
c.syntax "preview-images [options]"
|
|
54
|
+
c.description "Generate AI preview/social images (Claude directs & reviews; " \
|
|
55
|
+
"OpenAI/xAI/Stability/Gemini/local renders)"
|
|
56
|
+
SWITCHES.each { |_flag, merc| c.option(*merc) }
|
|
57
|
+
|
|
58
|
+
c.action do |_args, options|
|
|
59
|
+
new.run(options)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def run(options)
|
|
66
|
+
python = ENV["PYTHON"] || "python3"
|
|
67
|
+
ensure_python!(python)
|
|
68
|
+
argv = build_argv(options)
|
|
69
|
+
Jekyll.logger.info "preview-images:", "#{python} #{ENGINE} #{argv.join(' ')}"
|
|
70
|
+
exec(python, ENGINE, *argv)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def build_argv(options)
|
|
74
|
+
SWITCHES.each_with_object([]) do |(flag, merc), argv|
|
|
75
|
+
value = options[merc.first.to_s]
|
|
76
|
+
next if value.nil? || value == false
|
|
77
|
+
|
|
78
|
+
if BOOLEANS.include?(flag)
|
|
79
|
+
argv << flag
|
|
80
|
+
else
|
|
81
|
+
argv << flag << value.to_s
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
private
|
|
87
|
+
|
|
88
|
+
def ensure_python!(python)
|
|
89
|
+
unless command_available?(python)
|
|
90
|
+
Jekyll.logger.abort_with "preview-images:",
|
|
91
|
+
"#{python} not found — install Python 3.9+ " \
|
|
92
|
+
"(or set PYTHON=/path/to/python3)."
|
|
93
|
+
end
|
|
94
|
+
return if system(python, "-c", "import yaml", out: File::NULL, err: File::NULL)
|
|
95
|
+
|
|
96
|
+
Jekyll.logger.abort_with "preview-images:",
|
|
97
|
+
"PyYAML is missing — run: #{python} -m pip install pyyaml"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def command_available?(cmd)
|
|
101
|
+
system(cmd, "--version", out: File::NULL, err: File::NULL)
|
|
102
|
+
rescue SystemCallError
|
|
103
|
+
false
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|