@orbitant/brain-marketing 1.5.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/.claude-plugin/plugin.json +9 -0
- package/README.md +48 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.js +45 -0
- package/manifest.json +226 -0
- package/package.json +28 -0
- package/skills/blog-post-create/SKILL.md +362 -0
- package/skills/blog-post-create/references/orbitant-activation-framework.md +189 -0
- package/skills/blog-post-create/references/orbitant-narrative.md +151 -0
- package/skills/blog-post-review/SKILL.md +206 -0
- package/skills/blog-post-translate/SKILL.md +147 -0
- package/skills/image-creation/README.md +78 -0
- package/skills/image-creation/SKILL.md +251 -0
- package/skills/image-creation/assets/watermark-black.svg +11 -0
- package/skills/image-creation/assets/watermark-white.svg +11 -0
- package/skills/image-creation/references/visual-identity.md +49 -0
- package/skills/image-creation/scripts/generate-image.mjs +310 -0
- package/skills/image-creation/scripts/scrape-insights-images.mjs +144 -0
- package/skills/linkedin-post/SKILL.md +291 -0
- package/skills/newsletter/SKILL.md +193 -0
- package/skills/tone/SKILL.md +218 -0
- package/skills/yt-description/SKILL.md +210 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: orbitant-image-creation
|
|
3
|
+
description: |
|
|
4
|
+
Generates blog post thumbnail images for Orbitant following the brand's visual
|
|
5
|
+
identity, using Google's Imagen API (Nano Banana 2). Activates when creating
|
|
6
|
+
blog images, generating thumbnails, designing featured images for articles, or
|
|
7
|
+
when someone needs a visual for an Orbitant insight/blog post. Use this skill
|
|
8
|
+
even if the user just says "I need an image for this article", "create a
|
|
9
|
+
thumbnail", "generate a hero image", or "make a featured image". Also triggers
|
|
10
|
+
when the user mentions "Nano Banana 2", "image generation", or asks for a
|
|
11
|
+
prompt for an AI image tool.
|
|
12
|
+
license: MIT
|
|
13
|
+
version: "1.0.0"
|
|
14
|
+
metadata:
|
|
15
|
+
author: orbitant
|
|
16
|
+
tags: marketing, image, thumbnail, blog, visual, prompt, nano-banana-2, ai-image, imagen
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Overview
|
|
20
|
+
|
|
21
|
+
You are a visual prompt engineer and image generator for the Orbitant engineering blog. Your job is to:
|
|
22
|
+
|
|
23
|
+
1. Craft a prompt matching Orbitant's visual identity
|
|
24
|
+
2. Generate the image by running the bundled script against Google's Imagen API
|
|
25
|
+
3. Deliver the final image file to the user
|
|
26
|
+
|
|
27
|
+
Respond in the same language as the user's request.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## When to Use This Skill
|
|
32
|
+
|
|
33
|
+
Activate when the user:
|
|
34
|
+
- Needs a featured image or thumbnail for a blog post
|
|
35
|
+
- Asks for an image prompt for an Orbitant article
|
|
36
|
+
- Wants to generate visuals matching Orbitant's brand style
|
|
37
|
+
- Mentions Nano Banana 2 or AI image generation for blog content
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Prerequisites
|
|
42
|
+
|
|
43
|
+
### Dependencies
|
|
44
|
+
|
|
45
|
+
- **Node.js 18+**
|
|
46
|
+
- **`@google/genai`** and **`sharp`** packages:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm install @google/genai sharp
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Google API Key
|
|
53
|
+
|
|
54
|
+
You need a `GOOGLE_API_KEY` to call the Imagen API. There are two ways to get one:
|
|
55
|
+
|
|
56
|
+
| Option | How | Cost |
|
|
57
|
+
|--------|-----|------|
|
|
58
|
+
| **Google AI Studio** | Go to <https://aistudio.google.com/apikey> and create a key with your personal Google account | Free tier with daily limits |
|
|
59
|
+
| **Google Workspace** | Same link, but sign in with your organization's Workspace account. Many Workspace plans include Gemini/Imagen API access with generous quotas (check your admin console under **Apps → Additional Google services → Google AI Studio**) | Included in Workspace plans that have Gemini enabled |
|
|
60
|
+
|
|
61
|
+
> **Tip:** If your organization uses Google Workspace with Gemini enabled, you likely already have API access at no extra cost — ask your Workspace admin if unsure.
|
|
62
|
+
|
|
63
|
+
### Setting the API Key
|
|
64
|
+
|
|
65
|
+
Pick **one** of the following methods:
|
|
66
|
+
|
|
67
|
+
**Option A — `.env` file (recommended, stays local and git-ignored):**
|
|
68
|
+
|
|
69
|
+
Create `plugins/orbitant-marketing/skills/image-creation/scripts/.env`:
|
|
70
|
+
|
|
71
|
+
```env
|
|
72
|
+
GOOGLE_API_KEY=your-key-here
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Option B — Environment variable (current shell session only):**
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
export GOOGLE_API_KEY="your-key-here"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Option C — Shell profile (persistent across sessions):**
|
|
82
|
+
|
|
83
|
+
Add to your `~/.bashrc`, `~/.zshrc`, or equivalent:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
export GOOGLE_API_KEY="your-key-here"
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
> **Note:** The `.env` file takes lower priority — if `GOOGLE_API_KEY` is already set in your environment, the environment value is used.
|
|
90
|
+
|
|
91
|
+
### Quick Verification
|
|
92
|
+
|
|
93
|
+
Run a single test image to confirm everything works:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
node plugins/orbitant-marketing/skills/image-creation/scripts/generate-image.mjs \
|
|
97
|
+
--prompt "A single white ceramic cube on a white surface, soft studio lighting, shallow depth of field, minimalist, monochrome" \
|
|
98
|
+
--output /tmp/orbitant-test.png \
|
|
99
|
+
--count 1
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
If the API key is not set, the skill will craft the prompt and show it to the user so they can use it manually in AI Studio or another tool.
|
|
103
|
+
|
|
104
|
+
> **Note:** The `--negative` flag is accepted by the script but **not supported by the current Imagen API** (`imagen-4.0-generate-001`). Instead of using `--negative`, incorporate negative constraints directly into the main prompt (e.g., "No red, orange, or yellow fire. No text, no words, no logos.").
|
|
105
|
+
|
|
106
|
+
### Reference Images Setup
|
|
107
|
+
|
|
108
|
+
Before crafting prompts, check if `assets/reference/` contains images. These are real blog thumbnails from orbitant.com that show the target visual style by example.
|
|
109
|
+
|
|
110
|
+
If the folder is **empty or missing**, ask the user to run:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
node scripts/scrape-insights-images.mjs
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
This downloads a curated set of ~26 reference images. It is safe to re-run — existing files are skipped. Use `--force` to re-download everything.
|
|
117
|
+
|
|
118
|
+
Once available, **browse a few reference images** from `assets/reference/` before crafting prompts. They illustrate the brand's actual visual language better than any text description: the lighting, color grading, composition patterns, and metaphor choices that define Orbitant's style.
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Available Scripts
|
|
123
|
+
|
|
124
|
+
- **`scripts/generate-image.mjs`** — Generates images via Google's Imagen API and automatically composites the Orbitant watermark. Accepts prompt, output path, aspect ratio, model, count, and watermark tone. Returns JSON with file paths on success.
|
|
125
|
+
- **`scripts/scrape-insights-images.mjs`** — Downloads curated reference images from orbitant.com into `assets/reference/`. Skips existing files. Use `--force` to re-download.
|
|
126
|
+
|
|
127
|
+
Run `node scripts/generate-image.mjs --help` or `node scripts/scrape-insights-images.mjs --help` for full usage.
|
|
128
|
+
|
|
129
|
+
## Available Assets
|
|
130
|
+
|
|
131
|
+
- **`assets/watermark-white.svg`** — White Orbitant watermark (compass + text) for dark backgrounds
|
|
132
|
+
- **`assets/watermark-black.svg`** — Black Orbitant watermark for light backgrounds
|
|
133
|
+
|
|
134
|
+
The script auto-detects which watermark to use based on the bottom strip brightness of the generated image. Override with `--watermark white|black|none`.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Workflow
|
|
139
|
+
|
|
140
|
+
### Step 0 — Load Visual References
|
|
141
|
+
|
|
142
|
+
1. Read `references/visual-identity.md` for the brand rules.
|
|
143
|
+
2. Check if `assets/reference/` contains images. If empty, ask the user to run `node scripts/scrape-insights-images.mjs` and wait before continuing.
|
|
144
|
+
3. Browse 3–5 reference images from `assets/reference/` to calibrate your sense of the brand's visual style.
|
|
145
|
+
|
|
146
|
+
### Step 1 — Choose the Category
|
|
147
|
+
|
|
148
|
+
Based on the article topic, decide between:
|
|
149
|
+
- **Category A — Conceptual Metaphor** (default, ~70% of images): AI-generated scenes using a physical metaphor
|
|
150
|
+
- **Category B — Real Photography** (~30%): Team photos for culture/event articles — cannot be generated, tell the user to pick from their photo library
|
|
151
|
+
|
|
152
|
+
### Step 2 — Find the Metaphor (Category A only)
|
|
153
|
+
|
|
154
|
+
Identify a **physical object or scene** that metaphorically represents the article's core concept:
|
|
155
|
+
- Immediately recognizable (not too abstract)
|
|
156
|
+
- Visually simple (one subject, not a collage)
|
|
157
|
+
- Compatible with a studio-lit, minimalist aesthetic
|
|
158
|
+
- Not a stock-photo cliche (no handshakes, gears, lightbulbs, jigsaw pieces)
|
|
159
|
+
|
|
160
|
+
### Step 3 — Choose the Background Tone
|
|
161
|
+
|
|
162
|
+
- **Light background** (white/light gray): methodology, best practices, architecture, design, product, business
|
|
163
|
+
- **Dark background** (charcoal/black): security, debugging, infrastructure, AI, data, DevOps, low-level engineering
|
|
164
|
+
|
|
165
|
+
### Step 4 — Craft the Prompt
|
|
166
|
+
|
|
167
|
+
Use this structure:
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
A [object/scene metaphor] representing [concept], shot with a [lens mm] lens
|
|
171
|
+
at f/[aperture], [lighting type] from [direction]. [Background color] studio
|
|
172
|
+
background. Color palette: [colors]. Minimalist composition with generous
|
|
173
|
+
negative space. [Additional details]. Clean empty bottom-center area with no
|
|
174
|
+
elements or objects. Photorealistic quality, 16:9 aspect ratio.
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Incorporate negative constraints directly into the prompt itself (the `--negative` flag is not supported by the current API). Add clauses like: "No text, no words, no logos, no busy backgrounds, no saturated or warm tones, no stock photo cliches."
|
|
178
|
+
|
|
179
|
+
### Step 5 — Generate the Image
|
|
180
|
+
|
|
181
|
+
Run the script from the skill directory:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
node scripts/generate-image.mjs \
|
|
185
|
+
--prompt "THE CRAFTED PROMPT" \
|
|
186
|
+
--output ./output/ARTICLE-SLUG.png \
|
|
187
|
+
--aspect 16:9
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
To generate multiple variants for the user to choose from:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
node scripts/generate-image.mjs \
|
|
194
|
+
--prompt "THE CRAFTED PROMPT" \
|
|
195
|
+
--output ./output/ARTICLE-SLUG.png \
|
|
196
|
+
--aspect 16:9 \
|
|
197
|
+
--count 3
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Step 6 — Present Results
|
|
201
|
+
|
|
202
|
+
Show the user:
|
|
203
|
+
1. The **category** and **metaphor reasoning**
|
|
204
|
+
2. The **prompt** used (also saved as `.prompt.json` next to the images for reuse)
|
|
205
|
+
3. The **generated image(s)** — read the output file(s) so the user can see them
|
|
206
|
+
4. Note that the **Orbitant watermark was automatically composited** (unless `--watermark none` was used)
|
|
207
|
+
|
|
208
|
+
The `.prompt.json` file stores the full prompt, model, aspect ratio, and generation timestamp so the user can reproduce or tweak the image later without the skill.
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## Orbitant Visual Identity Rules
|
|
213
|
+
|
|
214
|
+
> Full visual identity spec (colors, watermark, signature look, proven metaphors) is in **`references/visual-identity.md`**. Read it before crafting prompts.
|
|
215
|
+
|
|
216
|
+
Key points:
|
|
217
|
+
- **Color palette**: Monochrome-dominant, teal (#00BFA5) accent only, avoid saturated/warm tones
|
|
218
|
+
- **Watermark**: Composited automatically — never include text/logos in the prompt, always leave clean bottom-center space
|
|
219
|
+
- **Signature look**: Shallow DoF, minimalist, studio-lit, desaturated premium aesthetic
|
|
220
|
+
- **Format**: 16:9 landscape, 1440x810, PNG from API
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## Example
|
|
225
|
+
|
|
226
|
+
**Article**: "5 Tips for Successful Legacy Migrations"
|
|
227
|
+
|
|
228
|
+
**Category**: A — Conceptual Metaphor
|
|
229
|
+
**Metaphor**: Layered architectural model being deconstructed, representing careful extraction of legacy systems
|
|
230
|
+
**Background**: Light (methodology/best-practices topic)
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
node scripts/generate-image.mjs \
|
|
234
|
+
--prompt "A detailed white architectural model of a classic building being carefully deconstructed layer by layer, with some layers floating slightly above, shot with a 85mm lens at f/2.8, soft directional lighting from the left. Clean white studio background. Color palette: monochrome whites and light grays with subtle shadows. Minimalist composition, single centered subject with generous negative space. Clean bottom-center area reserved for brand watermark. No text, no words, no logos, no busy backgrounds. Photorealistic 3D render quality, 16:9 aspect ratio." \
|
|
235
|
+
--output ./output/legacy-migrations.png \
|
|
236
|
+
--count 2
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## Important Rules
|
|
242
|
+
|
|
243
|
+
- **NEVER include text, words, or logos in the prompt** — the watermark is composited separately.
|
|
244
|
+
- **One subject, one metaphor** — Orbitant images are minimalist.
|
|
245
|
+
- **Respect the color palette** — desaturated, monochrome-dominant, teal accents only.
|
|
246
|
+
- **Always specify shallow depth of field** in the prompt.
|
|
247
|
+
- **Always reserve bottom-center space** for the logo overlay.
|
|
248
|
+
- **Avoid stock photo cliches** — no handshakes, gears, lightbulbs, jigsaw pieces, globes.
|
|
249
|
+
- **Match background tone to topic** — light for constructive topics, dark for technical/deep topics.
|
|
250
|
+
- **If the article is about team/culture**, recommend a real photo (Category B) instead of generating.
|
|
251
|
+
- **If `GOOGLE_API_KEY` is not available**, output the prompt for manual use and tell the user how to set up the key.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<svg width="805" height="155" viewBox="0 0 805 155" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M136.959 67.1554L126.89 66.0017C122.485 42.454 105.152 23.4166 82.548 16.5725L80.9222 2.36001C80.7648 1.02267 79.6373 0 78.2737 0C76.9102 0 75.7826 1.02267 75.6253 2.36001L74.2093 14.6583C70.879 14.1338 67.4701 13.8454 64.0088 13.8454C28.7135 13.8192 0 42.5064 0 77.8017C0 113.097 28.7135 141.81 64.0088 141.81C67.5226 141.81 70.9577 141.522 74.3141 140.971L75.6253 152.483C75.7826 153.82 76.9102 154.843 78.2737 154.843C79.6373 154.843 80.7648 153.82 80.9222 152.483L82.4693 139.083C108.797 131.138 128.018 106.673 128.018 77.8017C128.018 76.3595 127.939 74.9173 127.86 73.5013L136.986 72.4524C138.323 72.295 139.346 71.1675 139.346 69.8039C139.346 68.4403 138.323 67.3128 136.986 67.1554H136.959ZM115.824 64.7168L112.992 64.4021C89.628 61.7274 86.56 50.8714 83.8853 28.1628C99.6187 34.4824 111.629 48.0394 115.824 64.743V64.7168ZM64.0088 131.322C34.4562 131.322 10.4889 107.354 10.4889 77.8017C10.4889 48.2491 34.4562 24.3081 64.0088 24.3081C67.0768 24.3081 70.0923 24.6228 73.003 25.121L72.8719 26.301C70.1186 50.3207 67.5488 76.8839 43.5291 79.6373L24.8326 82.3906C23.4952 82.548 22.4726 83.6755 22.4726 85.0391C22.4726 86.4027 23.4952 87.5302 24.8326 87.6876L43.5291 90.4409C67.5488 93.1942 70.1186 104.522 72.8719 128.542L73.1079 130.482C70.1448 130.981 67.1292 131.322 64.0088 131.322ZM117.502 77.8017C117.502 100.353 103.526 119.6 83.7804 127.493C86.4813 103.762 89.3133 77.9066 112.992 75.2057L117.345 74.7075C117.397 75.7302 117.502 76.7528 117.502 77.8017Z" fill="#000000"/>
|
|
3
|
+
<path d="M216.727 125.002C208.729 125.002 201.754 123.167 195.828 119.522C189.901 115.877 185.313 110.527 182.061 103.447C178.809 96.3673 177.184 87.6877 177.184 77.4085C177.184 67.1294 178.809 58.3449 182.061 51.3173C185.313 44.2897 189.901 38.9403 195.828 35.2954C201.754 31.6505 208.729 29.8149 216.727 29.8149C224.725 29.8149 231.831 31.6505 237.757 35.2954C243.683 38.9403 248.272 44.2897 251.524 51.3173C254.776 58.3449 256.401 67.0507 256.401 77.4085C256.401 87.7663 254.776 96.3673 251.524 103.447C248.272 110.527 243.683 115.877 237.757 119.522C231.831 123.167 224.803 125.002 216.727 125.002ZM216.727 111.681C221.552 111.681 225.721 110.58 229.261 108.377C232.775 106.174 235.555 103.054 237.574 99.0157C239.593 94.9775 240.615 90.1788 240.615 84.6459V70.1449C240.615 64.5333 239.593 59.6822 237.574 55.644C235.555 51.6057 232.775 48.5115 229.261 46.3612C225.747 44.211 221.578 43.1359 216.727 43.1359C211.876 43.1359 207.942 44.211 204.324 46.3612C200.731 48.5115 197.925 51.6057 195.959 55.644C193.992 59.6822 192.996 64.5071 192.996 70.1449V84.6459C192.996 90.1788 193.992 94.9775 195.959 99.0157C197.925 103.054 200.731 106.174 204.324 108.377C207.916 110.58 212.059 111.681 216.727 111.681Z" fill="#000000"/>
|
|
4
|
+
<path d="M285.666 86.3766V123.429H270.771V31.4146H310.446C315.979 31.4146 320.699 32.5159 324.606 34.7186C328.513 36.9212 331.581 40.0679 333.758 44.1324C335.96 48.2231 337.062 53.1529 337.062 58.9742C337.062 65.3987 335.541 70.853 332.525 75.3894C329.51 79.9259 325.078 83.0464 319.283 84.8033L339.054 123.429H322.456L304.126 86.3766H285.666ZM285.666 73.7112H309.397C311.862 73.7112 313.986 73.2654 315.795 72.4001C317.605 71.5348 318.968 70.2236 319.938 68.5192C320.909 66.8147 321.381 64.7169 321.381 62.252V55.9325C321.381 53.3889 320.909 51.2649 319.938 49.6129C318.968 47.9346 317.578 46.676 315.795 45.7844C313.986 44.9191 311.862 44.4733 309.397 44.4733H289.599C287.423 44.4733 285.666 46.2302 285.666 48.4066V73.7374V73.7112Z" fill="#000000"/>
|
|
5
|
+
<path d="M352.898 123.429V31.4146H393.622C401.174 31.4146 407.152 33.5648 411.558 37.8652C415.963 42.1657 418.14 47.9346 418.14 55.1458C418.14 59.7085 417.327 63.3796 415.701 66.1592C414.075 68.9387 412.056 70.9579 409.644 72.2952C407.231 73.6063 404.74 74.8125 402.196 75.0748C404.137 75.1534 406.182 76.0188 408.385 76.8579C410.588 77.697 412.659 78.9557 414.652 80.6077C416.645 82.2597 418.245 84.4624 419.529 87.1371C420.814 89.8117 421.444 93.0895 421.444 96.9704C421.444 101.9 420.421 106.384 418.349 110.423C416.278 114.461 413.472 117.66 409.906 119.967C406.34 122.301 402.275 123.455 397.712 123.455H352.898V123.429ZM367.793 48.1182V70.2761H390.868C393.333 70.2761 395.405 69.8565 397.136 69.0174C398.84 68.1783 400.177 66.9458 401.095 65.3201C402.013 63.6943 402.485 61.7014 402.485 59.3151V55.0933C402.485 52.812 402.013 50.8453 401.095 49.2195C400.177 47.5937 398.866 46.3351 397.136 45.4697C395.431 44.6044 393.333 44.1586 390.868 44.1586H371.726C369.55 44.1586 367.793 45.9155 367.793 48.092V48.1182ZM367.793 82.6793V110.632H393.36C395.903 110.632 398.106 110.16 399.941 109.242C401.777 108.325 403.219 107.014 404.216 105.283C405.212 103.578 405.736 101.428 405.736 98.8847V94.4006C405.736 91.9357 405.238 89.838 404.216 88.0811C403.193 86.3242 401.777 84.9868 399.941 84.069C398.106 83.1513 395.903 82.6793 393.36 82.6793H367.793Z" fill="#000000"/>
|
|
6
|
+
<path d="M448.454 123.429H433.534L433.455 31.4146H448.454V123.429Z" fill="#000000"/>
|
|
7
|
+
<path d="M523.082 44.5785H498.276V123.403H483.382V44.5785H455.953V31.3887H527.829L523.056 44.5785H523.082Z" fill="#000000"/>
|
|
8
|
+
<path d="M587.064 123.429H571.514L563.464 98.5176H529.06L520.879 123.429H505.854L537.215 31.4146H555.807L587.038 123.429H587.064ZM559.793 85.7211L549.933 56.4045C548.701 52.7596 543.561 52.7596 542.355 56.4045L532.495 85.7211H559.793Z" fill="#000000"/>
|
|
9
|
+
<path d="M651.94 123.429L621.889 72.6885L618.245 65.8969C616.435 62.5405 611.348 63.8254 611.348 67.6276V123.429H597.109V31.4146H613.839L643.759 82.1548L646.801 88.0286C648.663 91.6473 654.143 90.3886 654.222 86.2979L654.3 81.7615V31.4146H668.539V123.429H651.94Z" fill="#000000"/>
|
|
10
|
+
<path d="M745.92 44.5785H721.114V123.403H706.22V44.5785H678.791V31.3887H748.516L745.894 44.5785H745.92Z" fill="#000000"/>
|
|
11
|
+
</svg>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<svg width="805" height="155" viewBox="0 0 805 155" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M136.959 67.1554L126.89 66.0017C122.485 42.454 105.152 23.4166 82.548 16.5725L80.9222 2.36001C80.7648 1.02267 79.6373 0 78.2737 0C76.9102 0 75.7826 1.02267 75.6253 2.36001L74.2093 14.6583C70.879 14.1338 67.4701 13.8454 64.0088 13.8454C28.7135 13.8192 0 42.5064 0 77.8017C0 113.097 28.7135 141.81 64.0088 141.81C67.5226 141.81 70.9577 141.522 74.3141 140.971L75.6253 152.483C75.7826 153.82 76.9102 154.843 78.2737 154.843C79.6373 154.843 80.7648 153.82 80.9222 152.483L82.4693 139.083C108.797 131.138 128.018 106.673 128.018 77.8017C128.018 76.3595 127.939 74.9173 127.86 73.5013L136.986 72.4524C138.323 72.295 139.346 71.1675 139.346 69.8039C139.346 68.4403 138.323 67.3128 136.986 67.1554H136.959ZM115.824 64.7168L112.992 64.4021C89.628 61.7274 86.56 50.8714 83.8853 28.1628C99.6187 34.4824 111.629 48.0394 115.824 64.743V64.7168ZM64.0088 131.322C34.4562 131.322 10.4889 107.354 10.4889 77.8017C10.4889 48.2491 34.4562 24.3081 64.0088 24.3081C67.0768 24.3081 70.0923 24.6228 73.003 25.121L72.8719 26.301C70.1186 50.3207 67.5488 76.8839 43.5291 79.6373L24.8326 82.3906C23.4952 82.548 22.4726 83.6755 22.4726 85.0391C22.4726 86.4027 23.4952 87.5302 24.8326 87.6876L43.5291 90.4409C67.5488 93.1942 70.1186 104.522 72.8719 128.542L73.1079 130.482C70.1448 130.981 67.1292 131.322 64.0088 131.322ZM117.502 77.8017C117.502 100.353 103.526 119.6 83.7804 127.493C86.4813 103.762 89.3133 77.9066 112.992 75.2057L117.345 74.7075C117.397 75.7302 117.502 76.7528 117.502 77.8017Z" fill="#FFFFFF"/>
|
|
3
|
+
<path d="M216.727 125.002C208.729 125.002 201.754 123.167 195.828 119.522C189.901 115.877 185.313 110.527 182.061 103.447C178.809 96.3673 177.184 87.6877 177.184 77.4085C177.184 67.1294 178.809 58.3449 182.061 51.3173C185.313 44.2897 189.901 38.9403 195.828 35.2954C201.754 31.6505 208.729 29.8149 216.727 29.8149C224.725 29.8149 231.831 31.6505 237.757 35.2954C243.683 38.9403 248.272 44.2897 251.524 51.3173C254.776 58.3449 256.401 67.0507 256.401 77.4085C256.401 87.7663 254.776 96.3673 251.524 103.447C248.272 110.527 243.683 115.877 237.757 119.522C231.831 123.167 224.803 125.002 216.727 125.002ZM216.727 111.681C221.552 111.681 225.721 110.58 229.261 108.377C232.775 106.174 235.555 103.054 237.574 99.0157C239.593 94.9775 240.615 90.1788 240.615 84.6459V70.1449C240.615 64.5333 239.593 59.6822 237.574 55.644C235.555 51.6057 232.775 48.5115 229.261 46.3612C225.747 44.211 221.578 43.1359 216.727 43.1359C211.876 43.1359 207.942 44.211 204.324 46.3612C200.731 48.5115 197.925 51.6057 195.959 55.644C193.992 59.6822 192.996 64.5071 192.996 70.1449V84.6459C192.996 90.1788 193.992 94.9775 195.959 99.0157C197.925 103.054 200.731 106.174 204.324 108.377C207.916 110.58 212.059 111.681 216.727 111.681Z" fill="#FFFFFF"/>
|
|
4
|
+
<path d="M285.666 86.3766V123.429H270.771V31.4146H310.446C315.979 31.4146 320.699 32.5159 324.606 34.7186C328.513 36.9212 331.581 40.0679 333.758 44.1324C335.96 48.2231 337.062 53.1529 337.062 58.9742C337.062 65.3987 335.541 70.853 332.525 75.3894C329.51 79.9259 325.078 83.0464 319.283 84.8033L339.054 123.429H322.456L304.126 86.3766H285.666ZM285.666 73.7112H309.397C311.862 73.7112 313.986 73.2654 315.795 72.4001C317.605 71.5348 318.968 70.2236 319.938 68.5192C320.909 66.8147 321.381 64.7169 321.381 62.252V55.9325C321.381 53.3889 320.909 51.2649 319.938 49.6129C318.968 47.9346 317.578 46.676 315.795 45.7844C313.986 44.9191 311.862 44.4733 309.397 44.4733H289.599C287.423 44.4733 285.666 46.2302 285.666 48.4066V73.7374V73.7112Z" fill="#FFFFFF"/>
|
|
5
|
+
<path d="M352.898 123.429V31.4146H393.622C401.174 31.4146 407.152 33.5648 411.558 37.8652C415.963 42.1657 418.14 47.9346 418.14 55.1458C418.14 59.7085 417.327 63.3796 415.701 66.1592C414.075 68.9387 412.056 70.9579 409.644 72.2952C407.231 73.6063 404.74 74.8125 402.196 75.0748C404.137 75.1534 406.182 76.0188 408.385 76.8579C410.588 77.697 412.659 78.9557 414.652 80.6077C416.645 82.2597 418.245 84.4624 419.529 87.1371C420.814 89.8117 421.444 93.0895 421.444 96.9704C421.444 101.9 420.421 106.384 418.349 110.423C416.278 114.461 413.472 117.66 409.906 119.967C406.34 122.301 402.275 123.455 397.712 123.455H352.898V123.429ZM367.793 48.1182V70.2761H390.868C393.333 70.2761 395.405 69.8565 397.136 69.0174C398.84 68.1783 400.177 66.9458 401.095 65.3201C402.013 63.6943 402.485 61.7014 402.485 59.3151V55.0933C402.485 52.812 402.013 50.8453 401.095 49.2195C400.177 47.5937 398.866 46.3351 397.136 45.4697C395.431 44.6044 393.333 44.1586 390.868 44.1586H371.726C369.55 44.1586 367.793 45.9155 367.793 48.092V48.1182ZM367.793 82.6793V110.632H393.36C395.903 110.632 398.106 110.16 399.941 109.242C401.777 108.325 403.219 107.014 404.216 105.283C405.212 103.578 405.736 101.428 405.736 98.8847V94.4006C405.736 91.9357 405.238 89.838 404.216 88.0811C403.193 86.3242 401.777 84.9868 399.941 84.069C398.106 83.1513 395.903 82.6793 393.36 82.6793H367.793Z" fill="#FFFFFF"/>
|
|
6
|
+
<path d="M448.454 123.429H433.534L433.455 31.4146H448.454V123.429Z" fill="#FFFFFF"/>
|
|
7
|
+
<path d="M523.082 44.5785H498.276V123.403H483.382V44.5785H455.953V31.3887H527.829L523.056 44.5785H523.082Z" fill="#FFFFFF"/>
|
|
8
|
+
<path d="M587.064 123.429H571.514L563.464 98.5176H529.06L520.879 123.429H505.854L537.215 31.4146H555.807L587.038 123.429H587.064ZM559.793 85.7211L549.933 56.4045C548.701 52.7596 543.561 52.7596 542.355 56.4045L532.495 85.7211H559.793Z" fill="#FFFFFF"/>
|
|
9
|
+
<path d="M651.94 123.429L621.889 72.6885L618.245 65.8969C616.435 62.5405 611.348 63.8254 611.348 67.6276V123.429H597.109V31.4146H613.839L643.759 82.1548L646.801 88.0286C648.663 91.6473 654.143 90.3886 654.222 86.2979L654.3 81.7615V31.4146H668.539V123.429H651.94Z" fill="#FFFFFF"/>
|
|
10
|
+
<path d="M745.92 44.5785H721.114V123.403H706.22V44.5785H678.791V31.3887H748.516L745.894 44.5785H745.92Z" fill="#FFFFFF"/>
|
|
11
|
+
</svg>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Orbitant Visual Identity Rules
|
|
2
|
+
|
|
3
|
+
Every Orbitant blog thumbnail follows a strict visual system. These are non-negotiable.
|
|
4
|
+
|
|
5
|
+
## Format
|
|
6
|
+
|
|
7
|
+
- **Aspect ratio**: 16:9 landscape
|
|
8
|
+
- **Resolution**: 1440x810 or equivalent
|
|
9
|
+
- **Output**: PNG from the API, JPG for final upload
|
|
10
|
+
|
|
11
|
+
## The Orbitant Watermark
|
|
12
|
+
|
|
13
|
+
The watermark is **composited automatically** by the script — never ask the model to render text or logos.
|
|
14
|
+
- Compass-star icon + "ORBITANT" in bold sans-serif uppercase
|
|
15
|
+
- White on dark backgrounds, black on light backgrounds (auto-detected)
|
|
16
|
+
- Bottom center, ~5% from bottom edge, ~20% of image width
|
|
17
|
+
- Always instruct the model to leave clean space at bottom center in the prompt
|
|
18
|
+
|
|
19
|
+
## Color Palette
|
|
20
|
+
|
|
21
|
+
| Role | Colors |
|
|
22
|
+
|------|--------|
|
|
23
|
+
| **Primary** | Monochrome — white, light gray (#F5F5F5), charcoal (#2D2D2D), black |
|
|
24
|
+
| **Accent** | Teal/cyan (#00BFA5) — sparingly in UI elements, glows, icons |
|
|
25
|
+
| **Occasional** | Deep navy blue, muted indigo — dark tech scenes |
|
|
26
|
+
| **Avoid** | Saturated primary colors (red, yellow, bright green), warm tones, pastels |
|
|
27
|
+
|
|
28
|
+
## Signature Look
|
|
29
|
+
|
|
30
|
+
- **Shallow depth of field** (bokeh/blur) — always
|
|
31
|
+
- **Minimalist composition** — single centered subject, generous negative space
|
|
32
|
+
- **Studio-lit feel** — soft directional lighting, subtle shadows
|
|
33
|
+
- **Desaturated, premium** — Apple/Stripe aesthetic, not stock photo
|
|
34
|
+
|
|
35
|
+
## Proven Metaphors from Real Orbitant Images
|
|
36
|
+
|
|
37
|
+
| Topic | Metaphor |
|
|
38
|
+
|-------|----------|
|
|
39
|
+
| Clean architecture | White architectural scale model on white background |
|
|
40
|
+
| Modular systems | White building blocks on white surface |
|
|
41
|
+
| AI security threats | Wireframe brain with glassmorphism panels, dark background |
|
|
42
|
+
| DevOps/CI-CD | Server room with motion-blur lights |
|
|
43
|
+
| Digital transformation | Person with binary code projected on face |
|
|
44
|
+
| Legacy migration | Blurred cityscape with digital art installation |
|
|
45
|
+
| UX design | Design tool interface on monitor, shallow DoF |
|
|
46
|
+
| Remote work | Video call grid on monitor |
|
|
47
|
+
| AI-powered tools | Laptop with dark UI + floating glassmorphism overlays, teal accents |
|
|
48
|
+
| Debugging | Close-up of code on screen, monochrome with bokeh |
|
|
49
|
+
| Microchips/low-level | Macro circuit board, blue-indigo tones |
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generates Orbitant blog thumbnails using Google's Imagen API
|
|
5
|
+
* and composites the Orbitant watermark automatically.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* node scripts/generate-image.mjs --prompt "..." --output path/to/image.png [OPTIONS]
|
|
9
|
+
*
|
|
10
|
+
* Options:
|
|
11
|
+
* --prompt TEXT Image generation prompt (required)
|
|
12
|
+
* --output FILE Output file path, .png (required)
|
|
13
|
+
* --negative TEXT Negative prompt (optional)
|
|
14
|
+
* --aspect RATIO 1:1, 3:4, 4:3, 9:16, 16:9 (default: 16:9)
|
|
15
|
+
* --model MODEL Model ID (default: imagen-4.0-generate-001)
|
|
16
|
+
* --count N Number of images, 1-4 (default: 1)
|
|
17
|
+
* --watermark TONE Watermark variant: white, black, none (default: auto-detect)
|
|
18
|
+
* --help Show this help
|
|
19
|
+
*
|
|
20
|
+
* Environment:
|
|
21
|
+
* GOOGLE_API_KEY Required. Get one at https://aistudio.google.com/apikey
|
|
22
|
+
*
|
|
23
|
+
* Exit codes:
|
|
24
|
+
* 0 Success
|
|
25
|
+
* 1 Missing/invalid arguments
|
|
26
|
+
* 2 Missing GOOGLE_API_KEY
|
|
27
|
+
* 3 API or processing error
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import { writeFile, readFile, mkdir, access } from "node:fs/promises";
|
|
31
|
+
import { dirname, extname, join } from "node:path";
|
|
32
|
+
import { fileURLToPath } from "node:url";
|
|
33
|
+
import { parseArgs } from "node:util";
|
|
34
|
+
|
|
35
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
36
|
+
|
|
37
|
+
// Load .env file from the scripts directory if it exists
|
|
38
|
+
const envPath = join(__dirname, ".env");
|
|
39
|
+
try {
|
|
40
|
+
await access(envPath);
|
|
41
|
+
const envContent = await readFile(envPath, "utf-8");
|
|
42
|
+
for (const line of envContent.split("\n")) {
|
|
43
|
+
const trimmed = line.trim();
|
|
44
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
45
|
+
const eqIndex = trimmed.indexOf("=");
|
|
46
|
+
if (eqIndex === -1) continue;
|
|
47
|
+
const key = trimmed.slice(0, eqIndex).trim();
|
|
48
|
+
const value = trimmed.slice(eqIndex + 1).trim();
|
|
49
|
+
if (!process.env[key]) {
|
|
50
|
+
process.env[key] = value;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
} catch {
|
|
54
|
+
// No .env file — rely on environment variables
|
|
55
|
+
}
|
|
56
|
+
const ASSETS_DIR = join(__dirname, "..", "assets");
|
|
57
|
+
|
|
58
|
+
const HELP = `Usage: node scripts/generate-image.mjs --prompt "..." --output path/to/image.png [OPTIONS]
|
|
59
|
+
|
|
60
|
+
Options:
|
|
61
|
+
--prompt TEXT Image generation prompt (required)
|
|
62
|
+
--output FILE Output file path, .png (required)
|
|
63
|
+
--negative TEXT Negative prompt (optional)
|
|
64
|
+
--aspect RATIO 1:1, 3:4, 4:3, 9:16, 16:9 (default: 16:9)
|
|
65
|
+
--model MODEL Model ID (default: imagen-4.0-generate-001)
|
|
66
|
+
--count N Number of images, 1-4 (default: 1)
|
|
67
|
+
--watermark TONE white, black, or none (default: auto-detect from image brightness)
|
|
68
|
+
--help Show this help
|
|
69
|
+
|
|
70
|
+
Environment:
|
|
71
|
+
GOOGLE_API_KEY Required. Get one at https://aistudio.google.com/apikey`;
|
|
72
|
+
|
|
73
|
+
function parseCliArgs() {
|
|
74
|
+
const { values } = parseArgs({
|
|
75
|
+
options: {
|
|
76
|
+
prompt: { type: "string" },
|
|
77
|
+
output: { type: "string" },
|
|
78
|
+
negative: { type: "string", default: "" },
|
|
79
|
+
aspect: { type: "string", default: "16:9" },
|
|
80
|
+
model: { type: "string", default: "imagen-4.0-generate-001" },
|
|
81
|
+
count: { type: "string", default: "3" },
|
|
82
|
+
watermark: { type: "string", default: "auto" },
|
|
83
|
+
help: { type: "boolean", default: false },
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
return values;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Detects whether the bottom strip of an image is dark or light
|
|
91
|
+
* to choose the right watermark color.
|
|
92
|
+
*/
|
|
93
|
+
async function detectWatermarkTone(sharp, imageBuffer) {
|
|
94
|
+
const metadata = await sharp(imageBuffer).metadata();
|
|
95
|
+
const { width, height } = metadata;
|
|
96
|
+
|
|
97
|
+
// Sample the bottom 10% of the image
|
|
98
|
+
const stripHeight = Math.round(height * 0.1);
|
|
99
|
+
const stats = await sharp(imageBuffer)
|
|
100
|
+
.extract({ left: 0, top: height - stripHeight, width, height: stripHeight })
|
|
101
|
+
.stats();
|
|
102
|
+
|
|
103
|
+
// Average brightness across RGB channels
|
|
104
|
+
const avgBrightness =
|
|
105
|
+
(stats.channels[0].mean + stats.channels[1].mean + stats.channels[2].mean) / 3;
|
|
106
|
+
|
|
107
|
+
return avgBrightness < 128 ? "white" : "black";
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Composites the Orbitant watermark onto the generated image.
|
|
112
|
+
*/
|
|
113
|
+
async function addWatermark(sharp, imageBuffer, tone) {
|
|
114
|
+
const watermarkPath = join(ASSETS_DIR, `watermark-${tone}.svg`);
|
|
115
|
+
const watermarkSvg = await readFile(watermarkPath);
|
|
116
|
+
|
|
117
|
+
const metadata = await sharp(imageBuffer).metadata();
|
|
118
|
+
const { width, height } = metadata;
|
|
119
|
+
|
|
120
|
+
// Scale watermark to ~20% of image width
|
|
121
|
+
const wmWidth = Math.round(width * 0.2);
|
|
122
|
+
const wmHeight = Math.round(wmWidth * (155 / 805)); // preserve SVG aspect ratio
|
|
123
|
+
|
|
124
|
+
const resizedWatermark = await sharp(watermarkSvg)
|
|
125
|
+
.resize(wmWidth, wmHeight)
|
|
126
|
+
.png()
|
|
127
|
+
.toBuffer();
|
|
128
|
+
|
|
129
|
+
// Position: centered horizontally, 5% from bottom
|
|
130
|
+
const left = Math.round((width - wmWidth) / 2);
|
|
131
|
+
const top = Math.round(height - height * 0.05 - wmHeight);
|
|
132
|
+
|
|
133
|
+
const result = await sharp(imageBuffer)
|
|
134
|
+
.composite([{ input: resizedWatermark, left, top }])
|
|
135
|
+
.png()
|
|
136
|
+
.toBuffer();
|
|
137
|
+
|
|
138
|
+
return result;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function main() {
|
|
142
|
+
const args = parseCliArgs();
|
|
143
|
+
|
|
144
|
+
if (args.help) {
|
|
145
|
+
console.log(HELP);
|
|
146
|
+
process.exit(0);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (!args.prompt) {
|
|
150
|
+
console.error("Error: --prompt is required.\n");
|
|
151
|
+
console.error(HELP);
|
|
152
|
+
process.exit(1);
|
|
153
|
+
}
|
|
154
|
+
if (!args.output) {
|
|
155
|
+
console.error("Error: --output is required.\n");
|
|
156
|
+
console.error(HELP);
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
if (extname(args.output) !== ".png") {
|
|
160
|
+
console.error(
|
|
161
|
+
`Error: --output must end in .png. Received: "${args.output}"`
|
|
162
|
+
);
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const apiKey = process.env.GOOGLE_API_KEY;
|
|
167
|
+
if (!apiKey) {
|
|
168
|
+
console.error(
|
|
169
|
+
"Error: GOOGLE_API_KEY environment variable is not set.\n" +
|
|
170
|
+
"Get one at: https://aistudio.google.com/apikey"
|
|
171
|
+
);
|
|
172
|
+
process.exit(2);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const validAspects = ["1:1", "3:4", "4:3", "9:16", "16:9"];
|
|
176
|
+
if (!validAspects.includes(args.aspect)) {
|
|
177
|
+
console.error(
|
|
178
|
+
`Error: --aspect must be one of: ${validAspects.join(", ")}.\n` +
|
|
179
|
+
` Received: "${args.aspect}"`
|
|
180
|
+
);
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const validWatermarks = ["auto", "white", "black", "none"];
|
|
185
|
+
if (!validWatermarks.includes(args.watermark)) {
|
|
186
|
+
console.error(
|
|
187
|
+
`Error: --watermark must be one of: ${validWatermarks.join(", ")}.\n` +
|
|
188
|
+
` Received: "${args.watermark}"`
|
|
189
|
+
);
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const count = parseInt(args.count, 10);
|
|
194
|
+
if (isNaN(count) || count < 1 || count > 4) {
|
|
195
|
+
console.error("Error: --count must be between 1 and 4.");
|
|
196
|
+
process.exit(1);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Load dependencies
|
|
200
|
+
let GoogleGenAI, sharp;
|
|
201
|
+
try {
|
|
202
|
+
const genaiMod = await import("@google/genai");
|
|
203
|
+
GoogleGenAI = genaiMod.GoogleGenAI;
|
|
204
|
+
} catch {
|
|
205
|
+
console.error(
|
|
206
|
+
"Error: @google/genai package not found.\n" +
|
|
207
|
+
"Install it: npm install @google/genai"
|
|
208
|
+
);
|
|
209
|
+
process.exit(1);
|
|
210
|
+
}
|
|
211
|
+
try {
|
|
212
|
+
const sharpMod = await import("sharp");
|
|
213
|
+
sharp = sharpMod.default;
|
|
214
|
+
} catch {
|
|
215
|
+
console.error(
|
|
216
|
+
"Error: sharp package not found.\n" + "Install it: npm install sharp"
|
|
217
|
+
);
|
|
218
|
+
process.exit(1);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const ai = new GoogleGenAI({ apiKey });
|
|
222
|
+
|
|
223
|
+
console.error(`Generating ${count} image(s) with ${args.model}...`);
|
|
224
|
+
console.error(`Aspect ratio: ${args.aspect}`);
|
|
225
|
+
console.error(`Prompt: ${args.prompt.substring(0, 120)}...`);
|
|
226
|
+
|
|
227
|
+
let response;
|
|
228
|
+
try {
|
|
229
|
+
const config = {
|
|
230
|
+
numberOfImages: count,
|
|
231
|
+
aspectRatio: args.aspect,
|
|
232
|
+
};
|
|
233
|
+
if (args.negative) {
|
|
234
|
+
config.negativePrompt = args.negative;
|
|
235
|
+
}
|
|
236
|
+
response = await ai.models.generateImages({
|
|
237
|
+
model: args.model,
|
|
238
|
+
prompt: args.prompt,
|
|
239
|
+
config,
|
|
240
|
+
});
|
|
241
|
+
} catch (err) {
|
|
242
|
+
console.error(`API error: ${err.message}`);
|
|
243
|
+
process.exit(3);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (!response.generatedImages || response.generatedImages.length === 0) {
|
|
247
|
+
console.error(
|
|
248
|
+
"Error: API returned no images. The prompt may have been blocked by safety filters."
|
|
249
|
+
);
|
|
250
|
+
process.exit(3);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
await mkdir(dirname(args.output), { recursive: true });
|
|
254
|
+
|
|
255
|
+
const results = [];
|
|
256
|
+
for (let i = 0; i < response.generatedImages.length; i++) {
|
|
257
|
+
const imgBytes = response.generatedImages[i].image.imageBytes;
|
|
258
|
+
const originalBuffer = Buffer.from(imgBytes, "base64");
|
|
259
|
+
|
|
260
|
+
// Determine output paths
|
|
261
|
+
const basePath =
|
|
262
|
+
count === 1
|
|
263
|
+
? args.output
|
|
264
|
+
: args.output.replace(".png", `-${i + 1}.png`);
|
|
265
|
+
const brandedPath = basePath.replace(".png", "_branded.png");
|
|
266
|
+
|
|
267
|
+
// Save original (no watermark)
|
|
268
|
+
await writeFile(basePath, originalBuffer);
|
|
269
|
+
const originalSizeKB = Math.round(originalBuffer.length / 1024);
|
|
270
|
+
console.error(`Saved original: ${basePath} (${originalSizeKB} KB)`);
|
|
271
|
+
results.push({ path: basePath, sizeKB: originalSizeKB, type: "original" });
|
|
272
|
+
|
|
273
|
+
// Save branded (with watermark)
|
|
274
|
+
if (args.watermark !== "none") {
|
|
275
|
+
try {
|
|
276
|
+
const tone =
|
|
277
|
+
args.watermark === "auto"
|
|
278
|
+
? await detectWatermarkTone(sharp, originalBuffer)
|
|
279
|
+
: args.watermark;
|
|
280
|
+
const brandedBuffer = await addWatermark(sharp, originalBuffer, tone);
|
|
281
|
+
await writeFile(brandedPath, brandedBuffer);
|
|
282
|
+
const brandedSizeKB = Math.round(brandedBuffer.length / 1024);
|
|
283
|
+
console.error(`Saved branded: ${brandedPath} (${brandedSizeKB} KB) — watermark: ${tone} (${args.watermark === "auto" ? "auto-detected" : "manual"})`);
|
|
284
|
+
results.push({ path: brandedPath, sizeKB: brandedSizeKB, type: "branded" });
|
|
285
|
+
} catch (err) {
|
|
286
|
+
console.error(`Warning: watermark failed (${err.message}), branded version not saved`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Save prompt metadata alongside images for reproducibility
|
|
292
|
+
const promptFile = args.output.replace(".png", ".prompt.json");
|
|
293
|
+
const promptData = {
|
|
294
|
+
prompt: args.prompt,
|
|
295
|
+
negative: args.negative || null,
|
|
296
|
+
model: args.model,
|
|
297
|
+
aspect: args.aspect,
|
|
298
|
+
count,
|
|
299
|
+
watermark: args.watermark,
|
|
300
|
+
generatedAt: new Date().toISOString(),
|
|
301
|
+
images: results,
|
|
302
|
+
};
|
|
303
|
+
await writeFile(promptFile, JSON.stringify(promptData, null, 2));
|
|
304
|
+
console.error(`Saved prompt: ${promptFile}`);
|
|
305
|
+
|
|
306
|
+
// Structured output to stdout for the agent
|
|
307
|
+
console.log(JSON.stringify({ success: true, promptFile, images: results }, null, 2));
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
main();
|