git-hash-art 0.4.1 → 0.6.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.
package/ALGORITHM.md CHANGED
@@ -9,27 +9,35 @@ Hash String
9
9
 
10
10
  ├─► Seed (mulberry32 PRNG)
11
11
 
12
- ├─► Color Scheme (hash-driven variation + scheme type selection)
12
+ ├─► Archetype Selection (1 of 10 visual personalities)
13
13
 
14
- └─► Rendering Pipeline
14
+ ├─► Color Scheme (palette mode from archetype + temperature mode)
15
+
16
+ └─► Rendering Pipeline (parameters overridden by archetype)
15
17
 
16
- 1. Background Layer (radial gradient)
18
+ 0. Archetype Override (gridSize, layers, opacity, sizes, styles)
19
+ 1. Background Layer (7 styles: radial, linear, solid, multi-stop)
17
20
  1b. Layered Background (faint shapes + concentric rings)
18
21
  2. Composition Mode Selection
19
- 3. Focal Points + Void Zones (negative space)
22
+ 2b. Symmetry Mode Selection (none / bilateral / quad)
23
+ 3. Focal Points (rule-of-thirds biased) + Void Zones
20
24
  4. Flow Field Initialization
21
- 5. Shape Layers (× N layers)
25
+ 4b. Hero Shape (large focal anchor, archetype-controlled)
26
+ 5. Shape Layers (× N layers, archetype-tuned)
22
27
  │ ├─ Blend Mode (per-layer compositing)
23
- │ ├─ Render Style (fill+stroke, wireframe, dashed, watercolor, etc.)
28
+ │ ├─ Render Style (archetype-preferred + random mix)
24
29
  │ ├─ Position (composition mode + focal bias + density check)
25
30
  │ ├─ Shape Selection (layer-weighted)
26
31
  │ ├─ Atmospheric Depth (desaturation on later layers)
32
+ │ ├─ Temperature Contrast (foreground opposite to background)
27
33
  │ ├─ Styling (transparency, glow, gradients, color jitter)
28
34
  │ ├─ Organic Edges (~15% watercolor bleed)
29
35
  │ └─ Recursive Nesting (~15% of large shapes)
30
- 6. Flow-Line Pass (tapered brush strokes)
36
+ 6. Flow-Line Pass (tapered brush strokes, archetype-scaled)
37
+ 6b. Symmetry Mirroring (bilateral-x, bilateral-y, or quad)
31
38
  7. Noise Texture Overlay
32
- 8. Organic Connecting Curves
39
+ 8. Vignette (radial edge darkening)
40
+ 9. Organic Connecting Curves
33
41
  ```
34
42
 
35
43
  ## 1. Deterministic RNG
@@ -43,7 +51,44 @@ rng() → float in [0, 1)
43
51
 
44
52
  The old approach extracted 2-char hex pairs from the hash (only ~20 unique values in a 40-char hash). Mulberry32 produces a full 32-bit uniform stream from any seed, eliminating correlation artifacts.
45
53
 
46
- ## 2. Color Scheme
54
+ ## 2. Archetype System
55
+
56
+ Before any rendering begins, the hash deterministically selects one of 10 **visual archetypes** — fundamentally different rendering personalities that override key parameters. This is the primary mechanism for visual diversity: two hashes that select different archetypes will look like they came from entirely different generators.
57
+
58
+ Each archetype controls:
59
+
60
+ | Parameter | Effect |
61
+ |-----------|--------|
62
+ | `gridSize` | Shape density (2 = sparse, 9 = packed) |
63
+ | `layers` | Rendering depth (2 = flat, 5 = deep) |
64
+ | `baseOpacity` / `opacityReduction` | Transparency character |
65
+ | `minShapeSize` / `maxShapeSize` | Scale range |
66
+ | `backgroundStyle` | One of 7 background rendering modes |
67
+ | `paletteMode` | One of 7 color palette strategies |
68
+ | `preferredStyles` | Weighted render style selection |
69
+ | `flowLineMultiplier` | Flow line density (0 = none, 4 = heavy) |
70
+ | `heroShape` | Whether to draw a dominant focal shape |
71
+ | `glowMultiplier` | Glow probability scaling (0 = none, 3 = heavy) |
72
+ | `sizePower` | Size distribution curve (0.5 = uniform, 2.5 = many tiny) |
73
+
74
+ ### The 10 Archetypes
75
+
76
+ | Archetype | Character | Background | Palette | Key Traits |
77
+ |-----------|-----------|------------|---------|------------|
78
+ | **dense-chaotic** | Packed, energetic | radial-dark | harmonious | 9×9 grid, 5 layers, heavy flow lines, low glow |
79
+ | **minimal-spacious** | Clean, deliberate | solid-light | duotone | 2×2 grid, 2 layers, large shapes, no glow |
80
+ | **organic-flow** | Natural, flowing | radial-dark | earth | Heavy flow lines (4×), watercolor style, no hero |
81
+ | **geometric-precision** | Technical, structured | solid-dark | high-contrast | Stroke-only/dashed/hatched styles, no flow lines |
82
+ | **ethereal** | Dreamy, luminous | radial-light | pastel-light | High glow (2×), watercolor, hero shape |
83
+ | **bold-graphic** | Poster-like, impactful | linear-diagonal | duotone | 2 layers, very large shapes, no flow lines |
84
+ | **neon-glow** | Electric, vibrant | solid-dark | neon | Heavy glow (3×), stroke-heavy styles, hero shape |
85
+ | **monochrome-ink** | Pen-and-ink, textural | solid-light | monochrome | Hatched/incomplete styles, no glow |
86
+ | **cosmic** | Deep space, vast | radial-dark | neon | 8×8 grid, 5 layers, heavy glow, many tiny shapes |
87
+ | **classic** | Balanced, familiar | radial-dark | harmonious | Preserves the original rendering look |
88
+
89
+ Archetype values serve as defaults — explicit user config always wins. The `classic` archetype preserves backward compatibility with the original rendering style.
90
+
91
+ ## 3. Color Scheme
47
92
 
48
93
  The `SacredColorScheme` class derives three harmonious palettes from the hash:
49
94
 
@@ -53,7 +98,35 @@ The `SacredColorScheme` class derives three harmonious palettes from the hash:
53
98
  | Complementary | hue = seed + 180°, contrasting variation | Contrast accents |
54
99
  | Triadic | hue = seed + 120° | Additional variety |
55
100
 
56
- These are merged and deduplicated into a single 6-8 color palette. Background colors are darkened variants (65% and 55% brightness) of the base scheme.
101
+ These are merged and deduplicated into a single 6-8 color palette. Background colors are darkened variants (65% and 55% brightness) of the base scheme, with optional temperature shifting.
102
+
103
+ ### Palette Modes
104
+
105
+ The archetype's `paletteMode` reshapes the color palette to match the visual personality:
106
+
107
+ | Mode | Colors | Character |
108
+ |------|--------|-----------|
109
+ | **harmonious** | Full base + complementary + triadic | Rich, balanced (default) |
110
+ | **monochrome** | Single hue, 5 lightness steps | Elegant, focused |
111
+ | **duotone** | Two contrasting hues + tints | Bold, graphic |
112
+ | **neon** | 4 hues at full saturation | Electric, vivid |
113
+ | **pastel-light** | 4 hues at low saturation, high lightness | Soft, dreamy |
114
+ | **earth** | Warm muted naturals (browns, olives, sage) | Organic, grounded |
115
+ | **high-contrast** | Black + white + one accent | Technical, stark |
116
+
117
+ Each mode also provides matching background colors (e.g., neon gets near-black backgrounds, pastel-light gets warm off-whites).
118
+
119
+ ### Temperature Contrast
120
+
121
+ The hash deterministically selects a **temperature mode** that creates warm/cool tension across the image:
122
+
123
+ | Mode | Probability | Background | Foreground |
124
+ |------|-------------|------------|------------|
125
+ | `warm-bg` | ~40% | Hues shifted toward orange (30°) | Hues shifted toward blue (210°) |
126
+ | `cool-bg` | ~40% | Hues shifted toward blue (210°) | Hues shifted toward orange (30°) |
127
+ | `neutral` | ~20% | No temperature shift | No temperature shift |
128
+
129
+ The shift amount is subtle (15-25%) and increases on later layers, creating progressive temperature separation between foreground and background elements. This produces the kind of warm/cool interplay seen in classical painting.
57
130
 
58
131
  ### Hash-Driven Color Variation
59
132
 
@@ -75,11 +148,24 @@ Scheme types also vary: `analogic`, `mono`, `contrast`, `triade`, `tetrade`. The
75
148
  - **`hexWithAlpha(hex, alpha)`** — converts hex to `rgba()` for transparency
76
149
  - **`jitterColor(hex, rng, amount)`** — applies ±amount RGB jitter per channel for organic variation
77
150
  - **`desaturate(hex, amount)`** — blends toward luminance gray for atmospheric depth
151
+ - **`shiftTemperature(hex, target, amount)`** — shifts hue toward warm (orange) or cool (blue)
78
152
  - **Positional blending** — shape fill color is biased by canvas position, creating smooth color flow across the image
79
153
 
80
- ## 3. Background
154
+ ## 4. Background
155
+
156
+ The archetype's `backgroundStyle` selects one of 7 rendering modes:
81
157
 
82
- A radial gradient fills the canvas from center to corners using two darkened base-scheme colors. This creates depth before any shapes are drawn.
158
+ | Style | Description |
159
+ |-------|-------------|
160
+ | **radial-dark** | Radial gradient from dark center to darker edges (original default) |
161
+ | **radial-light** | Light off-white center fading to the base palette |
162
+ | **linear-horizontal** | Left-to-right gradient between two palette colors |
163
+ | **linear-diagonal** | Corner-to-corner gradient with color reversal |
164
+ | **solid-dark** | Flat dark color fill |
165
+ | **solid-light** | Flat warm off-white fill |
166
+ | **multi-stop** | 3-4 color gradient with a darkened mid-palette accent |
167
+
168
+ This single change has an outsized impact on visual diversity — a solid-light background with monochrome shapes looks nothing like a radial-dark background with neon glow.
83
169
 
84
170
  ### Layered Background
85
171
 
@@ -90,7 +176,7 @@ After the gradient, a second pass adds visual texture to the background:
90
176
 
91
177
  This prevents the background from feeling flat and gives the image depth before the main shape layers begin.
92
178
 
93
- ## 4. Composition Modes
179
+ ## 5. Composition Modes
94
180
 
95
181
  The hash deterministically selects one of five composition strategies that control how shapes are positioned on the canvas:
96
182
 
@@ -104,9 +190,31 @@ The hash deterministically selects one of five composition strategies that contr
104
190
 
105
191
  Each mode produces fundamentally different visual character from the same shape set.
106
192
 
107
- ## 5. Focal Points & Negative Space
193
+ ### Symmetry Modes
194
+
195
+ ~25% of hashes trigger a symmetry mode that mirrors the rendered content:
196
+
197
+ | Mode | Probability | Effect |
198
+ |------|-------------|--------|
199
+ | `bilateral-x` | 10% | Left half mirrored onto right half |
200
+ | `bilateral-y` | 10% | Top half mirrored onto bottom half |
201
+ | `quad` | 5% | Both axes mirrored (4-fold symmetry) |
202
+ | `none` | 75% | No mirroring |
203
+
204
+ Symmetry is applied after shape layers and flow lines but before post-processing (noise, vignette, connecting curves). This means the mirrored content gets the same noise texture and vignette as the original, maintaining visual consistency. Symmetrical generative art is disproportionately appealing to humans due to our innate preference for bilateral symmetry.
205
+
206
+ ## 6. Focal Points & Negative Space
108
207
 
109
- 1-2 focal points are placed on the canvas (kept away from edges). Every shape position is pulled toward the nearest focal point by a strength factor (30-70%), creating areas of visual density and intentional-looking composition rather than uniform scatter.
208
+ 1-2 focal points are placed on the canvas. **70% of the time**, focal points snap to **rule-of-thirds intersection points** (with slight jitter to avoid a mechanical look), creating compositions that feel intentionally designed. The remaining 30% use free placement within the central 60% of the canvas. Every shape position is pulled toward the nearest focal point by a strength factor (30-70%).
209
+
210
+ ### Hero Shape
211
+
212
+ ~60% of images receive a **hero shape** — a large sacred or complex geometry piece anchored at the primary focal point. The hero shape:
213
+ - Uses sacred/complex shape types (flower of life, fibonacci spiral, merkaba, fractal, etc.)
214
+ - Is sized at 80-130% of the maximum shape size for visual dominance
215
+ - Gets glow effects, gradient fills, and often watercolor rendering
216
+ - Is drawn before the main shape layers so other shapes layer on top of it
217
+ - Creates a clear center of gravity that anchors the entire composition
110
218
 
111
219
  ### Void Zones (Negative Space)
112
220
 
@@ -116,7 +224,7 @@ Each mode produces fundamentally different visual character from the same shape
116
224
 
117
225
  Before placing each shape, the renderer checks how many shapes already exist nearby. If local density exceeds ~15% of the per-layer shape count, there's a 60% chance the shape is skipped. This prevents areas from becoming an opaque blob and creates natural visual rhythm.
118
226
 
119
- ## 6. Shape Layers
227
+ ## 7. Shape Layers
120
228
 
121
229
  The image is built in N layers (default: 4). Each layer has its own characteristics:
122
230
 
@@ -129,7 +237,7 @@ The image is built in N layers (default: 4). Each layer has its own characterist
129
237
  | Shape weights | Early layers favor basic shapes; later layers favor complex/sacred |
130
238
  | Per-shape opacity | Additional random jitter (50-100% of layer opacity) |
131
239
  | Blend mode | Each layer gets a hash-derived `globalCompositeOperation` (see below) |
132
- | Render style | Each layer has a dominant render style; 30% of shapes pick their own |
240
+ | Render style | Each layer has a dominant render style (60% from archetype preferences, 40% random); 30% of shapes pick their own |
133
241
  | Atmospheric depth | Later layers desaturate colors by up to 30%, simulating distance |
134
242
 
135
243
  ### Blend Modes (Per-Layer Compositing)
@@ -142,12 +250,14 @@ Instead of always `fill()` + `stroke()`, each shape gets a rendering treatment:
142
250
 
143
251
  | Style | Description | Probability |
144
252
  |-------|-------------|-------------|
145
- | `fill-and-stroke` | Classic solid fill with outline | ~29% (weighted) |
146
- | `fill-only` | Soft shapes with no outline | ~14% |
147
- | `stroke-only` | Wireframe with ghost fill at 30% alpha | ~14% |
148
- | `double-stroke` | Outer stroke at 2× width + inner stroke in fill color | ~14% |
149
- | `dashed` | Dashed outline (5% size dash, 3% gap) | ~14% |
150
- | `watercolor` | 3-4 slightly offset passes at low opacity for bleed effect | ~14% |
253
+ | `fill-and-stroke` | Classic solid fill with outline | ~22% (weighted) |
254
+ | `fill-only` | Soft shapes with no outline | ~11% |
255
+ | `stroke-only` | Wireframe with ghost fill at 30% alpha | ~11% |
256
+ | `double-stroke` | Outer stroke at 2× width + inner stroke in fill color | ~11% |
257
+ | `dashed` | Dashed outline (5% size dash, 3% gap) | ~11% |
258
+ | `watercolor` | 3-4 slightly offset passes at low opacity for bleed effect | ~11% |
259
+ | `hatched` | Cross-hatch texture fill clipped to shape boundary | ~11% |
260
+ | `incomplete` | Only 60-85% of outline drawn via dash patterns | ~11% |
151
261
 
152
262
  70% of shapes in a layer use the layer's dominant style; 30% pick independently. Additionally, ~15% of `fill-and-stroke` shapes are upgraded to `watercolor` for organic edge effects.
153
263
 
@@ -188,7 +298,7 @@ Each shape receives:
188
298
  - Sized at 15-40% of the parent
189
299
  - More transparent than the parent layer
190
300
 
191
- ## 7. Flow-Line Pass (Tapered Brush Strokes)
301
+ ## 8. Flow-Line Pass (Tapered Brush Strokes)
192
302
 
193
303
  6-16 flowing curves are drawn across the canvas, following the hash-derived vector field:
194
304
 
@@ -204,7 +314,7 @@ The flow field is defined by:
204
314
  angle(x, y) = baseAngle + sin(x/w × freq × 2π) × π/2 + cos(y/h × freq × 2π) × π/2
205
315
  ```
206
316
 
207
- ## 8. Noise Texture Overlay
317
+ ## 9. Noise Texture Overlay
208
318
 
209
319
  A dedicated noise RNG (seeded separately from the main RNG to avoid affecting shape generation) renders thousands of 1px dots across the canvas:
210
320
 
@@ -213,7 +323,16 @@ A dedicated noise RNG (seeded separately from the main RNG to avoid affecting sh
213
323
  - Very low opacity (1-4%)
214
324
  - Creates subtle film-grain texture that adds organic depth
215
325
 
216
- ## 9. Organic Connecting Curves
326
+ ## 9b. Vignette
327
+
328
+ A radial gradient overlay darkens the edges of the canvas, drawing the viewer's eye toward the center:
329
+
330
+ - Strength varies by hash: 25-45% maximum edge darkening
331
+ - The vignette begins fading at 60% of the canvas radius from center
332
+ - Applied after noise but before connecting curves, so the curves remain visible at edges
333
+ - Creates a natural "spotlight" effect that makes compositions feel more focused and photographic
334
+
335
+ ## 10. Organic Connecting Curves
217
336
 
218
337
  Quadratic bezier curves connect nearby shapes:
219
338
 
package/CHANGELOG.md CHANGED
@@ -4,8 +4,27 @@ All notable changes to this project will be documented in this file. Dates are d
4
4
 
5
5
  Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
6
6
 
7
+ #### [0.6.0](https://github.com/gfargo/git-hash-art/compare/0.5.0...0.6.0)
8
+
9
+ - feat: archetype system, palette modes, background variety, and CLI [`#12`](https://github.com/gfargo/git-hash-art/pull/12)
10
+ - feat: archetype system for dramatically different visual personalities [`2a1b919`](https://github.com/gfargo/git-hash-art/commit/2a1b919c51d3bfbd9d5c7a381ea5e104f81df2f6)
11
+ - feat: add CLI for generating art from terminal [`184372a`](https://github.com/gfargo/git-hash-art/commit/184372a584f68031bf44379f385f2e78691f98aa)
12
+ - docs: update ALGORITHM.md with archetype system, palette modes, and background styles [`4eccd07`](https://github.com/gfargo/git-hash-art/commit/4eccd077be9469c27caf60a0d8c463124ad7f9fa)
13
+
14
+ #### [0.5.0](https://github.com/gfargo/git-hash-art/compare/0.4.1...0.5.0)
15
+
16
+ > 19 March 2026
17
+
18
+ - feat: visual improvements — composition, color, and rendering upgrades [`#11`](https://github.com/gfargo/git-hash-art/pull/11)
19
+ - feat: warm/cool temperature contrast in color system [`550bc6a`](https://github.com/gfargo/git-hash-art/commit/550bc6a87929d7ebafa973079845e688a00de929)
20
+ - docs: update ALGORITHM.md with all new visual features [`a05de15`](https://github.com/gfargo/git-hash-art/commit/a05de1586684b91266f5a26ea7279fc601dd2202)
21
+ - feat: hatched and incomplete render styles for shape variety [`4f42f7e`](https://github.com/gfargo/git-hash-art/commit/4f42f7e30bac60239b69468e36ef5481dca5d007)
22
+
7
23
  #### [0.4.1](https://github.com/gfargo/git-hash-art/compare/0.4.0...0.4.1)
8
24
 
25
+ > 19 March 2026
26
+
27
+ - chore: release v0.4.1 [`c453061`](https://github.com/gfargo/git-hash-art/commit/c4530616bc40a540ce4d87a8124a1d2665f77376)
9
28
  - update README with new feature details [`1e3fcd7`](https://github.com/gfargo/git-hash-art/commit/1e3fcd771ea602b21c9c90cd3809d0d08ace5d3f)
10
29
  - update color variations and complementary logic [`9c8f4ba`](https://github.com/gfargo/git-hash-art/commit/9c8f4ba51e1a5730e4c4ddd59218db23d4719129)
11
30
 
package/bin/cli.js ADDED
@@ -0,0 +1,155 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { execSync } = require("child_process");
4
+ const { generateImageFromHash, saveImageToFile } = require("../dist/main.js");
5
+ const { PRESETS } = require("../dist/main.js");
6
+
7
+ const HELP = `
8
+ git-hash-art — Generate deterministic abstract art from git hashes.
9
+
10
+ Usage:
11
+ git-hash-art current [options] Generate art from HEAD commit
12
+ git-hash-art generate <hash> [options] Generate art from a specific hash
13
+
14
+ Options:
15
+ --width <n> Canvas width in pixels (default: 2048)
16
+ --height <n> Canvas height in pixels (default: 2048)
17
+ --output <dir> Output directory (default: ./output)
18
+ --preset <name> Use a built-in preset (overrides width/height)
19
+ --layers <n> Number of layers (default: 4)
20
+ --grid <n> Grid density (default: 5)
21
+ --list-presets List available presets
22
+
23
+ Examples:
24
+ npx git-hash-art current
25
+ npx git-hash-art current --preset instagram-square
26
+ npx git-hash-art generate abc123ff --width 1920 --height 1080
27
+ `;
28
+
29
+ function parseArgs(argv) {
30
+ const args = argv.slice(2);
31
+ const command = args[0];
32
+ const parsed = { command, hash: null, options: {} };
33
+
34
+ let i = 1;
35
+ if (command === "generate") {
36
+ parsed.hash = args[1];
37
+ i = 2;
38
+ }
39
+
40
+ while (i < args.length) {
41
+ const arg = args[i];
42
+ if (arg === "--width" && args[i + 1]) {
43
+ parsed.options.width = parseInt(args[i + 1], 10);
44
+ i += 2;
45
+ } else if (arg === "--height" && args[i + 1]) {
46
+ parsed.options.height = parseInt(args[i + 1], 10);
47
+ i += 2;
48
+ } else if (arg === "--output" && args[i + 1]) {
49
+ parsed.options.output = args[i + 1];
50
+ i += 2;
51
+ } else if (arg === "--preset" && args[i + 1]) {
52
+ parsed.options.preset = args[i + 1];
53
+ i += 2;
54
+ } else if (arg === "--layers" && args[i + 1]) {
55
+ parsed.options.layers = parseInt(args[i + 1], 10);
56
+ i += 2;
57
+ } else if (arg === "--grid" && args[i + 1]) {
58
+ parsed.options.gridSize = parseInt(args[i + 1], 10);
59
+ i += 2;
60
+ } else if (arg === "--list-presets") {
61
+ parsed.command = "list-presets";
62
+ i += 1;
63
+ } else if (arg === "--help" || arg === "-h") {
64
+ parsed.command = "help";
65
+ i += 1;
66
+ } else {
67
+ i += 1;
68
+ }
69
+ }
70
+
71
+ return parsed;
72
+ }
73
+
74
+ function getHeadHash() {
75
+ try {
76
+ return execSync("git rev-parse HEAD", { encoding: "utf-8" }).trim();
77
+ } catch {
78
+ console.error("Error: Not a git repository or git is not installed.");
79
+ process.exit(1);
80
+ }
81
+ }
82
+
83
+ function listPresets() {
84
+ console.log("\nAvailable presets:\n");
85
+ for (const [name, config] of Object.entries(PRESETS)) {
86
+ console.log(` ${name.padEnd(20)} ${config.width}x${config.height}`);
87
+ }
88
+ console.log("");
89
+ }
90
+
91
+ function run() {
92
+ const { command, hash, options } = parseArgs(process.argv);
93
+
94
+ if (!command || command === "help") {
95
+ console.log(HELP);
96
+ process.exit(0);
97
+ }
98
+
99
+ if (command === "list-presets") {
100
+ listPresets();
101
+ process.exit(0);
102
+ }
103
+
104
+ if (command !== "current" && command !== "generate") {
105
+ console.error(`Unknown command: ${command}\n`);
106
+ console.log(HELP);
107
+ process.exit(1);
108
+ }
109
+
110
+ let gitHash;
111
+ if (command === "current") {
112
+ gitHash = getHeadHash();
113
+ console.log(`Using HEAD: ${gitHash}`);
114
+ } else {
115
+ if (!hash) {
116
+ console.error("Error: Please provide a hash.\n");
117
+ console.log("Usage: git-hash-art generate <hash> [options]");
118
+ process.exit(1);
119
+ }
120
+ gitHash = hash;
121
+ }
122
+
123
+ // Build config from preset + overrides
124
+ let config = {};
125
+ if (options.preset) {
126
+ const preset = PRESETS[options.preset];
127
+ if (!preset) {
128
+ console.error(`Unknown preset: ${options.preset}`);
129
+ console.log("Use --list-presets to see available presets.");
130
+ process.exit(1);
131
+ }
132
+ config = { ...preset };
133
+ delete config.hash;
134
+ }
135
+
136
+ if (options.width) config.width = options.width;
137
+ if (options.height) config.height = options.height;
138
+ if (options.layers) config.layers = options.layers;
139
+ if (options.gridSize) config.gridSize = options.gridSize;
140
+
141
+ const outputDir = options.output || "./output";
142
+
143
+ try {
144
+ const buffer = generateImageFromHash(gitHash, config);
145
+ const w = config.width || 2048;
146
+ const h = config.height || 2048;
147
+ const outputPath = saveImageToFile(buffer, outputDir, gitHash, "", w, h);
148
+ console.log(`Done! Image saved to ${outputPath}`);
149
+ } catch (error) {
150
+ console.error("Generation failed:", error.message);
151
+ process.exit(1);
152
+ }
153
+ }
154
+
155
+ run();