📦 deps(thirdparty): update snapshots

This commit is contained in:
ci[bot]
2026-09-03 08:57:39 +00:00
parent 0b2b056032
commit 3294e63c9f
98 changed files with 3411 additions and 467 deletions
+43 -24
View File
@@ -1,6 +1,6 @@
---
name: design
description: "Comprehensive design skill: brand identity, design tokens, UI styling, logo generation (55 styles, Gemini or Atlas Cloud AI), corporate identity program (50 deliverables, CIP mockups), HTML presentations (Chart.js), banner design (22 styles, social/ads/web/print), icon design (15 styles, SVG, Gemini 3.1 Pro), social photos (HTML→screenshot, multi-platform). Actions: design logo, create CIP, generate mockups, build slides, design banner, generate icon, create social photos, social media images, brand identity, design system. Platforms: Facebook, Twitter, LinkedIn, YouTube, Instagram, Pinterest, TikTok, Threads, Google Ads."
description: "Comprehensive design skill: brand identity, design tokens, UI styling, logo generation (55 styles, Gemini, Atlas Cloud, or MuAPI AI), corporate identity program (50 deliverables, CIP mockups), HTML presentations (Chart.js), banner design (22 styles, social/ads/web/print), icon design (15 styles, SVG, Gemini 3.1 Pro), social photos (HTML→screenshot, multi-platform). Actions: design logo, create CIP, generate mockups, build slides, design banner, generate icon, create social photos, social media images, brand identity, design system. Platforms: Facebook, Twitter, LinkedIn, YouTube, Instagram, Pinterest, TikTok, Threads, Google Ads."
argument-hint: "[design-type] [context]"
license: MIT
metadata:
@@ -37,22 +37,27 @@ Unified design skill: brand, tokens, UI, logo, CIP, slides, banners, social phot
| Social media images/photos | Social Photos (built-in) | `references/social-photos-design.md` |
| SVG icons, icon sets | Icon (built-in) | `references/icon-design.md` |
## Script Paths
Script paths in this skill and its `references/` are relative to the directory that contains this SKILL.md, not to the project: `scripts/<file>` is this skill's own `scripts/` folder, and `../<skill>/scripts/<file>` is a sibling sub-skill installed alongside it. Build the full path from that directory (Claude Code reports it as the skill's base directory when the skill loads) and keep the working directory at the project root — the scripts read and write project files such as `docs/brand-guidelines.md`, `assets/design-tokens.json` or `src/` relative to it.
## Logo Design (Built-in)
55+ styles, 30 color palettes, 25 industry guides. Gemini Nano Banana models.
55+ styles, 30 color palettes, 25 industry guides. Gemini Nano Banana, Atlas
Cloud, and MuAPI image generation.
### Logo: Generate Design Brief
```bash
python3 ~/.claude/skills/design/scripts/logo/search.py "tech startup modern" --design-brief -p "BrandName"
python3 scripts/logo/search.py "tech startup modern" --design-brief -p "BrandName"
```
### Logo: Search Styles/Colors/Industries
```bash
python3 ~/.claude/skills/design/scripts/logo/search.py "minimalist clean" --domain style
python3 ~/.claude/skills/design/scripts/logo/search.py "tech professional" --domain color
python3 ~/.claude/skills/design/scripts/logo/search.py "healthcare medical" --domain industry
python3 scripts/logo/search.py "minimalist clean" --domain style
python3 scripts/logo/search.py "tech professional" --domain color
python3 scripts/logo/search.py "healthcare medical" --domain industry
```
### Logo: Generate with AI
@@ -60,9 +65,11 @@ python3 ~/.claude/skills/design/scripts/logo/search.py "healthcare medical" --do
**ALWAYS** generate output logo images with white background.
```bash
python3 ~/.claude/skills/design/scripts/logo/generate.py --brand "TechFlow" --style minimalist --industry tech
python3 ~/.claude/skills/design/scripts/logo/generate.py --prompt "coffee shop vintage badge" --style vintage
python3 ~/.claude/skills/design/scripts/logo/generate.py --brand "TechFlow" --provider atlas
python3 scripts/logo/generate.py --brand "TechFlow" --style minimalist --industry tech
python3 scripts/logo/generate.py --prompt "coffee shop vintage badge" --style vintage
python3 scripts/logo/generate.py --brand "TechFlow" --provider atlas
python3 scripts/logo/generate.py --brand "TechFlow" --provider muapi
python3 scripts/logo/generate.py --brand "TechFlow" --provider muapi --muapi-model nano-banana-pro
```
**IMPORTANT:** When scripts fail, try to fix them directly.
@@ -76,32 +83,32 @@ After generation, **ALWAYS** ask user about HTML preview via `AskUserQuestion`.
### CIP: Generate Brief
```bash
python3 ~/.claude/skills/design/scripts/cip/search.py "tech startup" --cip-brief -b "BrandName"
python3 scripts/cip/search.py "tech startup" --cip-brief -b "BrandName"
```
### CIP: Search Domains
```bash
python3 ~/.claude/skills/design/scripts/cip/search.py "business card letterhead" --domain deliverable
python3 ~/.claude/skills/design/scripts/cip/search.py "luxury premium elegant" --domain style
python3 ~/.claude/skills/design/scripts/cip/search.py "hospitality hotel" --domain industry
python3 ~/.claude/skills/design/scripts/cip/search.py "office reception" --domain mockup
python3 scripts/cip/search.py "business card letterhead" --domain deliverable
python3 scripts/cip/search.py "luxury premium elegant" --domain style
python3 scripts/cip/search.py "hospitality hotel" --domain industry
python3 scripts/cip/search.py "office reception" --domain mockup
```
### CIP: Generate Mockups
```bash
# With logo (RECOMMENDED)
python3 ~/.claude/skills/design/scripts/cip/generate.py --brand "TopGroup" --logo /path/to/logo.png --deliverable "business card" --industry "consulting"
python3 scripts/cip/generate.py --brand "TopGroup" --logo /path/to/logo.png --deliverable "business card" --industry "consulting"
# Full CIP set
python3 ~/.claude/skills/design/scripts/cip/generate.py --brand "TopGroup" --logo /path/to/logo.png --industry "consulting" --set
python3 scripts/cip/generate.py --brand "TopGroup" --logo /path/to/logo.png --industry "consulting" --set
# Pro model (4K text)
python3 ~/.claude/skills/design/scripts/cip/generate.py --brand "TopGroup" --logo logo.png --deliverable "business card" --model pro
python3 scripts/cip/generate.py --brand "TopGroup" --logo logo.png --deliverable "business card" --model pro
# Without logo
python3 ~/.claude/skills/design/scripts/cip/generate.py --brand "TechFlow" --deliverable "business card" --no-logo-prompt
python3 scripts/cip/generate.py --brand "TechFlow" --deliverable "business card" --no-logo-prompt
```
Models: `flash` (default, `gemini-2.5-flash-image`), `pro` (`gemini-3-pro-image-preview`)
@@ -109,7 +116,7 @@ Models: `flash` (default, `gemini-2.5-flash-image`), `pro` (`gemini-3-pro-image-
### CIP: Render HTML Presentation
```bash
python3 ~/.claude/skills/design/scripts/cip/render-html.py --brand "TopGroup" --industry "consulting" --images /path/to/cip-output
python3 scripts/cip/render-html.py --brand "TopGroup" --industry "consulting" --images /path/to/cip-output
```
**Tip:** If no logo exists, use Logo Design section above first.
@@ -184,21 +191,21 @@ Load `references/banner-sizes-and-styles.md` for complete sizes and styles refer
### Icon: Generate Single Icon
```bash
python3 ~/.claude/skills/design/scripts/icon/generate.py --prompt "settings gear" --style outlined
python3 ~/.claude/skills/design/scripts/icon/generate.py --prompt "shopping cart" --style filled --color "#6366F1"
python3 ~/.claude/skills/design/scripts/icon/generate.py --name "dashboard" --category navigation --style duotone
python3 scripts/icon/generate.py --prompt "settings gear" --style outlined
python3 scripts/icon/generate.py --prompt "shopping cart" --style filled --color "#6366F1"
python3 scripts/icon/generate.py --name "dashboard" --category navigation --style duotone
```
### Icon: Generate Batch Variations
```bash
python3 ~/.claude/skills/design/scripts/icon/generate.py --prompt "cloud upload" --batch 4 --output-dir ./icons
python3 scripts/icon/generate.py --prompt "cloud upload" --batch 4 --output-dir ./icons
```
### Icon: Multi-size Export
```bash
python3 ~/.claude/skills/design/scripts/icon/generate.py --prompt "user profile" --sizes "16,24,32,48" --output-dir ./icons
python3 scripts/icon/generate.py --prompt "user profile" --sizes "16,24,32,48" --output-dir ./icons
```
### Icon: Top Styles
@@ -304,8 +311,20 @@ python3 --version || python --version
```bash
export GEMINI_API_KEY="your-key" # https://aistudio.google.com/apikey
pip install google-genai pillow
# Optional MuAPI provider (no extra Python package required)
export MUAPI_API_KEY="your-key"
```
MuAPI uses the asynchronous model endpoint and prediction result API. See the
[MuAPI API reference](https://muapi.ai/docs/api-reference) for authentication
and the [nano-banana model contract](https://api.muapi.ai/api/v1/models/nano-banana)
or [nano-banana-pro model contract](https://api.muapi.ai/api/v1/models/nano-banana-pro)
for the current model-specific schemas. The logo generator supports both documented
model slugs and sends their shared required `prompt` plus optional `aspect_ratio`
fields; the Pro model also accepts an optional `resolution` field that this focused
logo workflow leaves at the provider default.
> **Note for Windows:** Use `python` instead of `pip` where needed (e.g., `python -m pip install ...`).
## Integration
@@ -16,49 +16,49 @@ Corporate Identity Program design with 50+ deliverables, 20 styles, 20 industrie
### CIP Brief (Start Here)
```bash
python3 ~/.claude/skills/design/scripts/cip/search.py "tech startup" --cip-brief -b "BrandName"
python3 scripts/cip/search.py "tech startup" --cip-brief -b "BrandName"
```
### Search Domains
```bash
# Deliverables
python3 ~/.claude/skills/design/scripts/cip/search.py "business card letterhead" --domain deliverable
python3 scripts/cip/search.py "business card letterhead" --domain deliverable
# Design styles
python3 ~/.claude/skills/design/scripts/cip/search.py "luxury premium elegant" --domain style
python3 scripts/cip/search.py "luxury premium elegant" --domain style
# Industry guidelines
python3 ~/.claude/skills/design/scripts/cip/search.py "hospitality hotel" --domain industry
python3 scripts/cip/search.py "hospitality hotel" --domain industry
# Mockup contexts
python3 ~/.claude/skills/design/scripts/cip/search.py "office reception" --domain mockup
python3 scripts/cip/search.py "office reception" --domain mockup
```
### Generate Mockups
```bash
# With logo (RECOMMENDED - uses image editing)
python3 ~/.claude/skills/design/scripts/cip/generate.py --brand "TopGroup" --logo /path/to/logo.png --deliverable "business card" --industry "consulting"
python3 scripts/cip/generate.py --brand "TopGroup" --logo /path/to/logo.png --deliverable "business card" --industry "consulting"
# Full CIP set with logo
python3 ~/.claude/skills/design/scripts/cip/generate.py --brand "TopGroup" --logo /path/to/logo.png --industry "consulting" --set
python3 scripts/cip/generate.py --brand "TopGroup" --logo /path/to/logo.png --industry "consulting" --set
# Pro model for 4K text rendering
python3 ~/.claude/skills/design/scripts/cip/generate.py --brand "TopGroup" --logo logo.png --deliverable "business card" --model pro
python3 scripts/cip/generate.py --brand "TopGroup" --logo logo.png --deliverable "business card" --model pro
# Custom deliverables with aspect ratio
python3 ~/.claude/skills/design/scripts/cip/generate.py --brand "GreenLeaf" --logo logo.png --industry "organic food" --deliverables "letterhead,packaging,vehicle" --ratio 16:9
python3 scripts/cip/generate.py --brand "GreenLeaf" --logo logo.png --industry "organic food" --deliverables "letterhead,packaging,vehicle" --ratio 16:9
# Without logo (AI generates interpretation)
python3 ~/.claude/skills/design/scripts/cip/generate.py --brand "TechFlow" --deliverable "business card" --no-logo-prompt
python3 scripts/cip/generate.py --brand "TechFlow" --deliverable "business card" --no-logo-prompt
```
### Render HTML Presentation
```bash
python3 ~/.claude/skills/design/scripts/cip/render-html.py --brand "TopGroup" --industry "consulting" --images /path/to/cip-output
python3 ~/.claude/skills/design/scripts/cip/render-html.py --brand "TopGroup" --industry "consulting" --images ./topgroup-cip --output presentation.html
python3 scripts/cip/render-html.py --brand "TopGroup" --industry "consulting" --images /path/to/cip-output
python3 scripts/cip/render-html.py --brand "TopGroup" --industry "consulting" --images ./topgroup-cip --output presentation.html
```
## Models
@@ -164,14 +164,14 @@ Application Code
**Brand:**
```bash
node .claude/skills/brand/scripts/inject-brand-context.cjs
node .claude/skills/brand/scripts/validate-asset.cjs <path>
node ../brand/scripts/inject-brand-context.cjs
node ../brand/scripts/validate-asset.cjs <path>
```
**Tokens:**
```bash
node .claude/skills/design-system/scripts/generate-tokens.cjs -c tokens.json
node .claude/skills/design-system/scripts/validate-tokens.cjs -d src/
node ../design-system/scripts/generate-tokens.cjs -c tokens.json
node ../design-system/scripts/validate-tokens.cjs -d src/
```
**Components:**
@@ -13,29 +13,29 @@ AI-powered SVG icon generation using Gemini 3.1 Pro Preview. 15 styles, 12 categ
### Generate Single Icon
```bash
python3 ~/.claude/skills/design/scripts/icon/generate.py --prompt "settings gear" --style outlined
python3 ~/.claude/skills/design/scripts/icon/generate.py --prompt "shopping cart" --style filled --color "#6366F1"
python3 ~/.claude/skills/design/scripts/icon/generate.py --name "dashboard" --category navigation --style duotone
python3 scripts/icon/generate.py --prompt "settings gear" --style outlined
python3 scripts/icon/generate.py --prompt "shopping cart" --style filled --color "#6366F1"
python3 scripts/icon/generate.py --name "dashboard" --category navigation --style duotone
```
### Generate Batch Variations
```bash
python3 ~/.claude/skills/design/scripts/icon/generate.py --prompt "cloud upload" --batch 4 --output-dir ./icons
python3 ~/.claude/skills/design/scripts/icon/generate.py --prompt "notification bell" --batch 6 --style outlined --output-dir ./icons
python3 scripts/icon/generate.py --prompt "cloud upload" --batch 4 --output-dir ./icons
python3 scripts/icon/generate.py --prompt "notification bell" --batch 6 --style outlined --output-dir ./icons
```
### Generate Multiple Sizes
```bash
python3 ~/.claude/skills/design/scripts/icon/generate.py --prompt "user profile" --sizes "16,24,32,48" --output-dir ./icons
python3 scripts/icon/generate.py --prompt "user profile" --sizes "16,24,32,48" --output-dir ./icons
```
### List Styles/Categories
```bash
python3 ~/.claude/skills/design/scripts/icon/generate.py --list-styles
python3 ~/.claude/skills/design/scripts/icon/generate.py --list-categories
python3 scripts/icon/generate.py --list-styles
python3 scripts/icon/generate.py --list-categories
```
## CLI Options
@@ -1,13 +1,13 @@
# Logo Design Reference
AI-powered logo design with 55+ styles, 30 color palettes, 25 industry guides. Gemini Nano Banana is the default provider; Atlas Cloud is also available as an explicit opt-in.
AI-powered logo design with 55+ styles, 30 color palettes, 25 industry guides. Gemini Nano Banana is the default provider; Atlas Cloud and MuAPI are also available as explicit opt-in providers.
## Scripts
| Script | Purpose |
|--------|---------|
| `scripts/logo/search.py` | Search styles, colors, industries; generate design briefs |
| `scripts/logo/generate.py` | Generate logos with Gemini Nano Banana or Atlas Cloud |
| `scripts/logo/generate.py` | Generate logos with Gemini Nano Banana, Atlas Cloud, or MuAPI |
| `scripts/logo/core.py` | BM25 search engine for logo data |
## Commands
@@ -15,20 +15,20 @@ AI-powered logo design with 55+ styles, 30 color palettes, 25 industry guides. G
### Design Brief (Start Here)
```bash
python3 ~/.claude/skills/design/scripts/logo/search.py "tech startup modern" --design-brief -p "BrandName"
python3 scripts/logo/search.py "tech startup modern" --design-brief -p "BrandName"
```
### Search Domains
```bash
# Styles
python3 ~/.claude/skills/design/scripts/logo/search.py "minimalist clean" --domain style
python3 scripts/logo/search.py "minimalist clean" --domain style
# Color palettes
python3 ~/.claude/skills/design/scripts/logo/search.py "tech professional" --domain color
python3 scripts/logo/search.py "tech professional" --domain color
# Industry guidelines
python3 ~/.claude/skills/design/scripts/logo/search.py "healthcare medical" --domain industry
python3 scripts/logo/search.py "healthcare medical" --domain industry
```
### Generate Logo
@@ -36,12 +36,14 @@ python3 ~/.claude/skills/design/scripts/logo/search.py "healthcare medical" --do
**ALWAYS** use white background for output logos.
```bash
python3 ~/.claude/skills/design/scripts/logo/generate.py --brand "TechFlow" --style minimalist --industry tech
python3 ~/.claude/skills/design/scripts/logo/generate.py --prompt "coffee shop vintage badge" --style vintage
python3 ~/.claude/skills/design/scripts/logo/generate.py --brand "TechFlow" --provider atlas
python3 scripts/logo/generate.py --brand "TechFlow" --style minimalist --industry tech
python3 scripts/logo/generate.py --prompt "coffee shop vintage badge" --style vintage
python3 scripts/logo/generate.py --brand "TechFlow" --provider atlas
python3 scripts/logo/generate.py --brand "TechFlow" --provider muapi
python3 scripts/logo/generate.py --brand "TechFlow" --provider muapi --muapi-model nano-banana-pro
```
Options: `--style`, `--industry`, `--prompt`, `--provider`, `--atlas-model`
Options: `--style`, `--industry`, `--prompt`, `--provider`, `--atlas-model`, `--muapi-model`
## Available Styles
@@ -93,4 +95,16 @@ pip install google-genai
# Optional Atlas Cloud provider (no extra Python package required)
export ATLASCLOUD_API_KEY="your-key"
# Optional MuAPI provider (no extra Python package required)
export MUAPI_API_KEY="your-key"
```
MuAPI uses the asynchronous model endpoint and prediction result API. See the
[MuAPI API reference](https://muapi.ai/docs/api-reference) for authentication
and the [nano-banana model contract](https://api.muapi.ai/api/v1/models/nano-banana)
or [nano-banana-pro model contract](https://api.muapi.ai/api/v1/models/nano-banana-pro)
for the current model-specific schemas. The logo generator supports both documented
model slugs and sends their shared required `prompt` plus optional `aspect_ratio`
fields; the Pro model also accepts an optional `resolution` field that this focused
logo workflow leaves at the provider default.
@@ -66,10 +66,10 @@
```bash
# Find formula for slide type
python .claude/skills/design-system/scripts/search-slides.py "problem agitation" -d copy
python ../design-system/scripts/search-slides.py "problem agitation" -d copy
# Get emotion-appropriate formula
python .claude/skills/design-system/scripts/search-slides.py "urgency cta" -d copy
python ../design-system/scripts/search-slides.py "urgency cta" -d copy
```
## Quick Reference
@@ -113,10 +113,10 @@
```bash
# Find layout for specific use
python .claude/skills/design-system/scripts/search-slides.py "metrics dashboard" -d layout
python ../design-system/scripts/search-slides.py "metrics dashboard" -d layout
# Contextual recommendation
python .claude/skills/design-system/scripts/search-slides.py "traction slide" \
python ../design-system/scripts/search-slides.py "traction slide" \
--context --position 4 --total 10
```
@@ -76,10 +76,10 @@ Pattern breaks at 1/3 and 2/3 positions create engagement peaks.
```bash
# Find strategy by goal
python .claude/skills/design-system/scripts/search-slides.py "investor pitch" -d strategy
python ../design-system/scripts/search-slides.py "investor pitch" -d strategy
# Get emotion arc
python .claude/skills/design-system/scripts/search-slides.py "series a funding" -d strategy --json
python ../design-system/scripts/search-slides.py "series a funding" -d strategy --json
```
## Matching Strategy to Context
@@ -427,7 +427,10 @@ Image Editing Mode:
action = check_logo_required(args.brand, skip_prompt=args.no_logo_prompt)
if action == 'generate':
print("\n💡 To generate a logo, use the logo-design skill:")
print(f" python ~/.claude/skills/design/scripts/logo/generate.py --brand \"{args.brand}\" --industry \"{args.industry}\"")
# Resolved from this file so the hint is correct from any cwd and in
# every install layout (plugin cache, project or --global install).
logo_script = Path(__file__).resolve().parents[1] / "logo" / "generate.py"
print(f" python \"{logo_script}\" --brand \"{args.brand}\" --industry \"{args.industry}\"")
print("\n Then re-run this command with --logo <generated_logo.png>")
sys.exit(0)
elif action == 'exit':
@@ -1,12 +1,15 @@
#!/usr/bin/env python3
"""Logo generation with Gemini or Atlas Cloud.
"""Logo generation with Gemini, Atlas Cloud, or MuAPI.
Gemini remains the default provider. Atlas Cloud is opt-in with
``--provider atlas`` and uses its asynchronous image generation API.
``--provider atlas`` and uses its asynchronous image generation API. MuAPI is
opt-in with ``--provider muapi`` and uses its asynchronous image generation API
with the selected model's prompt/aspect-ratio contract.
Models:
- Nano Banana (default): gemini-2.5-flash-image - fast, high-volume, low-latency
- Nano Banana Pro (--pro): gemini-3-pro-image-preview - professional quality, advanced reasoning
- MuAPI Nano Banana (--provider muapi): nano-banana - hosted asynchronous image generation
Usage:
python generate.py --prompt "tech startup logo minimalist blue"
@@ -14,6 +17,8 @@ Usage:
python generate.py --brand "TechFlow" --industry tech --style minimalist
python generate.py --brand "TechFlow" --pro # Use Nano Banana Pro model
python generate.py --brand "TechFlow" --provider atlas
python generate.py --brand "TechFlow" --provider muapi
python generate.py --brand "TechFlow" --provider muapi --muapi-model nano-banana-pro
Batch mode (generates multiple variants):
python generate.py --brand "Unikorn" --batch 9 --output-dir ./logos --pro
@@ -57,6 +62,7 @@ load_env()
# ============ CONFIGURATION ============
GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY")
ATLASCLOUD_API_KEY = os.environ.get("ATLASCLOUD_API_KEY")
MUAPI_API_KEY = os.environ.get("MUAPI_API_KEY")
# Gemini "Nano Banana" model configurations for image generation
GEMINI_FLASH = "gemini-2.5-flash-image" # Nano Banana: fast, high-volume, low-latency
@@ -65,9 +71,14 @@ GEMINI_PRO = "gemini-3-pro-image-preview" # Nano Banana Pro: professional quali
# Atlas Cloud model validated against the live model catalog and schema.
ATLAS_MODEL = "google/nano-banana-2-lite/text-to-image"
ATLAS_API_BASE = "https://api.atlascloud.ai/api/v1"
HTTP_USER_AGENT = "ui-ux-pro-max/2.5 (Atlas Cloud logo provider)"
MUAPI_MODEL = "nano-banana"
MUAPI_MODELS = ("nano-banana", "nano-banana-pro")
MUAPI_API_BASE = "https://api.muapi.ai/api/v1"
HTTP_USER_AGENT = "ui-ux-pro-max/2.5 (logo generation)"
ATLAS_POLL_INTERVAL = 2
ATLAS_MAX_POLLS = 90
MUAPI_POLL_INTERVAL = 2
MUAPI_MAX_POLLS = 90
# Supported aspect ratios
ASPECT_RATIOS = ["1:1", "16:9", "9:16", "4:3", "3:4"]
@@ -156,13 +167,13 @@ def _validate_public_https_url(url):
or parsed.username
or parsed.password
):
raise ValueError("Atlas Cloud returned an invalid media URL")
raise ValueError("Provider returned an invalid media URL")
hostname = parsed.hostname.lower().rstrip(".")
if hostname == "localhost" or hostname.endswith(
(".localhost", ".local", ".internal")
):
raise ValueError("Atlas Cloud media URL used a local hostname")
raise ValueError("Provider media URL used a local hostname")
try:
ip = ipaddress.ip_address(hostname)
@@ -170,17 +181,26 @@ def _validate_public_https_url(url):
return
else:
if not ip.is_global:
raise ValueError("Atlas Cloud media URL used a non-public address")
raise ValueError("Provider media URL used a non-public address")
def _json_request(url, api_key, method="GET", payload=None):
def _json_request(
url, api_key, method="GET", payload=None, api_key_header="Authorization"
):
if api_key_header == "Authorization":
auth_value = f"Bearer {api_key}"
elif api_key_header == "x-api-key":
auth_value = api_key
else:
raise ValueError("Unsupported API key header")
body = json.dumps(payload).encode("utf-8") if payload is not None else None
request = Request(
url,
data=body,
method=method,
headers={
"Authorization": f"Bearer {api_key}",
api_key_header: auth_value,
"Accept": "application/json",
"User-Agent": HTTP_USER_AGENT,
**({"Content-Type": "application/json"} if body is not None else {}),
@@ -192,10 +212,10 @@ def _json_request(url, api_key, method="GET", payload=None):
except HTTPError as exc:
detail = exc.read().decode("utf-8", errors="replace")
raise RuntimeError(
f"Atlas Cloud request failed ({exc.code}): {detail[:300]}"
f"Provider request failed ({exc.code}): {detail[:300]}"
) from exc
except (URLError, TimeoutError, json.JSONDecodeError) as exc:
raise RuntimeError(f"Atlas Cloud request failed: {exc}") from exc
raise RuntimeError(f"Provider request failed: {exc}") from exc
def _atlas_prediction_data(response):
@@ -210,6 +230,10 @@ def _atlas_prediction_data(response):
def _download_atlas_image(url, output_path):
_download_image(url, output_path, "image provider")
def _download_image(url, output_path, provider_name):
_validate_public_https_url(url)
request = Request(
url,
@@ -222,14 +246,14 @@ def _download_atlas_image(url, output_path):
content_type = response.headers.get_content_type()
if not content_type.startswith("image/"):
raise RuntimeError(
f"Atlas Cloud output is not an image ({content_type})"
f"{provider_name} output is not an image ({content_type})"
)
image_data = response.read()
except (HTTPError, URLError, TimeoutError) as exc:
raise RuntimeError(f"Unable to download Atlas Cloud image: {exc}") from exc
raise RuntimeError(f"Unable to download {provider_name} image: {exc}") from exc
if not image_data:
raise RuntimeError("Atlas Cloud returned an empty image")
raise RuntimeError(f"{provider_name} returned an empty image")
with open(output_path, "wb") as output_file:
output_file.write(image_data)
@@ -281,6 +305,132 @@ def _generate_with_atlas(prompt, output_path, aspect_ratio, api_key, model):
raise RuntimeError("Atlas Cloud prediction timed out while polling")
def _muapi_response_objects(response):
"""Return the response and common MuAPI envelopes without guessing fields."""
if not isinstance(response, dict):
raise TypeError("MuAPI returned an invalid response")
objects = [response]
for key in ("data", "output", "result"):
value = response.get(key)
if isinstance(value, dict) and value not in objects:
objects.append(value)
return objects
def _muapi_response_value(response, keys):
for item in _muapi_response_objects(response):
for key in keys:
value = item.get(key)
if value not in (None, ""):
return value
return None
def _muapi_error(response):
value = _muapi_response_value(response, ("error", "message", "detail"))
if isinstance(value, str):
return value[:300]
return "MuAPI request failed"
def _muapi_result_url(response):
"""Return the documented result URL from the creation response."""
for item in _muapi_response_objects(response):
urls = item.get("urls")
if not isinstance(urls, dict) or "get" not in urls:
continue
result_url = urls.get("get")
if not isinstance(result_url, str) or not result_url:
raise RuntimeError(
"MuAPI creation response did not include a valid HTTPS result URL"
)
try:
_validate_public_https_url(result_url)
except ValueError as exc:
raise RuntimeError(
"MuAPI creation response did not include a valid HTTPS result URL"
) from exc
return result_url
raise RuntimeError(
"MuAPI creation response did not include a valid HTTPS result URL"
)
def _muapi_output_url(response):
for item in _muapi_response_objects(response):
outputs = item.get("outputs")
if isinstance(outputs, list):
for output in outputs:
if isinstance(output, str) and output.startswith("https://"):
return output
if isinstance(output, dict):
for key in ("url", "image_url"):
value = output.get(key)
if isinstance(value, str) and value.startswith("https://"):
return value
raise RuntimeError("MuAPI completed without an HTTPS image URL")
def _download_muapi_image(url, output_path):
_download_image(url, output_path, "MuAPI")
def _generate_with_muapi(prompt, output_path, aspect_ratio, api_key, model):
if not api_key:
raise RuntimeError("MUAPI_API_KEY not set")
if model not in MUAPI_MODELS:
raise RuntimeError(
f"Unsupported MuAPI logo model: {model}. "
f"Choose one of: {', '.join(MUAPI_MODELS)}"
)
payload = {
"prompt": prompt,
"aspect_ratio": aspect_ratio,
}
response = _json_request(
f"{MUAPI_API_BASE}/{model}",
api_key,
method="POST",
payload=payload,
api_key_header="x-api-key",
)
request_id = _muapi_response_value(response, ("request_id", "id"))
if not isinstance(request_id, str) or not request_id:
raise RuntimeError("MuAPI did not return a request ID")
result_url = _muapi_result_url(response)
data = response
for poll_number in range(MUAPI_MAX_POLLS + 1):
status = _muapi_response_value(data, ("status",))
normalized_status = str(status or "").lower()
if normalized_status in {"completed", "succeeded", "success"}:
_download_muapi_image(_muapi_output_url(data), output_path)
return
if normalized_status in {
"failed",
"error",
"timeout",
"canceled",
"cancelled",
}:
raise RuntimeError(f"MuAPI generation {normalized_status}: {_muapi_error(data)}")
if poll_number == MUAPI_MAX_POLLS:
break
time.sleep(MUAPI_POLL_INTERVAL)
data = _json_request(
result_url,
api_key,
api_key_header="x-api-key",
)
raise RuntimeError("MuAPI prediction timed out while polling")
def _generate_with_gemini(prompt, output_path, aspect_ratio, use_pro):
if not GEMINI_API_KEY:
raise RuntimeError("GEMINI_API_KEY not set")
@@ -344,8 +494,9 @@ def generate_logo(
aspect_ratio=None,
provider="gemini",
atlas_model=ATLAS_MODEL,
muapi_model=MUAPI_MODEL,
):
"""Generate a logo using Gemini or Atlas Cloud image generation.
"""Generate a logo using Gemini, Atlas Cloud, or MuAPI image generation.
Args:
aspect_ratio: Image aspect ratio. Options: "1:1", "16:9", "9:16", "4:3", "3:4"
@@ -365,6 +516,8 @@ def generate_logo(
if provider == "atlas":
model_label = f"Atlas Cloud ({atlas_model})"
elif provider == "muapi":
model_label = f"MuAPI ({muapi_model})"
else:
model_label = (
"Nano Banana Pro (gemini-3-pro-image-preview)"
@@ -386,6 +539,14 @@ def generate_logo(
ATLASCLOUD_API_KEY,
atlas_model,
)
elif provider == "muapi":
_generate_with_muapi(
full_prompt,
output_path,
ratio,
MUAPI_API_KEY,
muapi_model,
)
else:
_generate_with_gemini(full_prompt, output_path, ratio, use_pro)
@@ -407,6 +568,7 @@ def generate_batch(
aspect_ratio=None,
provider="gemini",
atlas_model=ATLAS_MODEL,
muapi_model=MUAPI_MODEL,
):
"""Generate multiple logo variants with different styles"""
@@ -430,6 +592,8 @@ def generate_batch(
model_label = (
f"Atlas Cloud ({atlas_model})"
if provider == "atlas"
else f"MuAPI ({muapi_model})"
if provider == "muapi"
else f"Nano Banana {'Pro' if use_pro else 'Flash'}"
)
ratio = aspect_ratio if aspect_ratio in ASPECT_RATIOS else DEFAULT_ASPECT_RATIO
@@ -466,6 +630,7 @@ def generate_batch(
aspect_ratio=aspect_ratio,
provider=provider,
atlas_model=atlas_model,
muapi_model=muapi_model,
)
if result:
@@ -487,7 +652,7 @@ def generate_batch(
def main():
parser = argparse.ArgumentParser(
description="Generate logos using Gemini or Atlas Cloud"
description="Generate logos using Gemini, Atlas Cloud, or MuAPI"
)
parser.add_argument("--prompt", "-p", type=str, help="Logo description prompt")
parser.add_argument("--brand", "-b", type=str, help="Brand name")
@@ -514,7 +679,7 @@ def main():
)
parser.add_argument(
"--provider",
choices=["gemini", "atlas"],
choices=["gemini", "atlas", "muapi"],
default="gemini",
help="Image provider (default: gemini)",
)
@@ -523,6 +688,12 @@ def main():
default=ATLAS_MODEL,
help=f"Atlas Cloud image model (default: {ATLAS_MODEL})",
)
parser.add_argument(
"--muapi-model",
choices=MUAPI_MODELS,
default=MUAPI_MODEL,
help=f"MuAPI image model (default: {MUAPI_MODEL})",
)
parser.add_argument(
"--aspect-ratio",
"-r",
@@ -539,8 +710,11 @@ def main():
args = parser.parse_args()
if args.provider == "atlas" and args.pro:
parser.error("--pro is only available with --provider gemini")
if args.provider != "gemini" and args.pro:
parser.error(
"--pro is only available with --provider gemini; "
"use --muapi-model nano-banana-pro for MuAPI"
)
if args.list_styles:
print("Available styles:")
@@ -574,6 +748,7 @@ def main():
aspect_ratio=args.aspect_ratio,
provider=args.provider,
atlas_model=args.atlas_model,
muapi_model=args.muapi_model,
)
else:
generate_logo(
@@ -586,6 +761,7 @@ def main():
aspect_ratio=args.aspect_ratio,
provider=args.provider,
atlas_model=args.atlas_model,
muapi_model=args.muapi_model,
)
@@ -128,5 +128,161 @@ class AtlasGenerationTests(unittest.TestCase):
logo_generate._validate_public_https_url("https://assets.local/logo.png")
class MuapiGenerationTests(unittest.TestCase):
@patch.object(logo_generate, "_download_muapi_image")
@patch.object(logo_generate.time, "sleep")
@patch.object(logo_generate, "_json_request")
def test_muapi_submits_once_and_polls_until_completed(
self, json_request, sleep, download
):
json_request.side_effect = [
{
"id": "req-123",
"status": "created",
"output": {
"urls": {
"get": "https://api.muapi.ai/api/v1/results/req-123"
}
},
},
{"id": "req-123", "status": "processing"},
{
"id": "req-123",
"status": "completed",
"output": {"outputs": ["https://media.example.com/logo.png"]},
},
]
logo_generate._generate_with_muapi(
"logo prompt", "logo.png", "1:1", "muapi-key", "nano-banana"
)
self.assertEqual(json_request.call_count, 3)
self.assertEqual(
json_request.call_args_list[0],
call(
f"{logo_generate.MUAPI_API_BASE}/nano-banana",
"muapi-key",
method="POST",
payload={"prompt": "logo prompt", "aspect_ratio": "1:1"},
api_key_header="x-api-key",
),
)
self.assertEqual(
json_request.call_args_list[1:],
[
call(
"https://api.muapi.ai/api/v1/results/req-123",
"muapi-key",
api_key_header="x-api-key",
),
call(
"https://api.muapi.ai/api/v1/results/req-123",
"muapi-key",
api_key_header="x-api-key",
),
],
)
self.assertEqual(sleep.call_count, 2)
download.assert_called_once_with(
"https://media.example.com/logo.png", "logo.png"
)
@patch.object(logo_generate, "_json_request")
def test_muapi_does_not_retry_generation_post(self, json_request):
json_request.side_effect = RuntimeError("network error")
with self.assertRaisesRegex(RuntimeError, "network error"):
logo_generate._generate_with_muapi(
"logo prompt", "logo.png", "1:1", "muapi-key", "nano-banana"
)
json_request.assert_called_once()
def test_muapi_requires_key_and_known_model(self):
with self.assertRaisesRegex(RuntimeError, "MUAPI_API_KEY not set"):
logo_generate._generate_with_muapi(
"logo prompt", "logo.png", "1:1", None, "nano-banana"
)
with self.assertRaisesRegex(RuntimeError, "Unsupported MuAPI logo model"):
logo_generate._generate_with_muapi(
"logo prompt", "logo.png", "1:1", "muapi-key", "unknown-model"
)
@patch.object(logo_generate, "build_opener")
def test_muapi_uses_x_api_key_header(self, build_opener):
class Response:
def __enter__(self):
return self
def __exit__(self, *args):
return None
@staticmethod
def read():
return b"{}"
build_opener.return_value.open.return_value = Response()
logo_generate._json_request(
"https://api.muapi.ai/api/v1/nano-banana",
"muapi-key",
method="POST",
payload={"prompt": "logo"},
api_key_header="x-api-key",
)
request = build_opener.return_value.open.call_args.args[0]
headers = {key.lower(): value for key, value in request.header_items()}
self.assertEqual(headers["x-api-key"], "muapi-key")
self.assertNotIn("authorization", headers)
@patch.object(logo_generate, "_json_request")
def test_muapi_reports_failed_prediction(self, json_request):
json_request.side_effect = [
{
"request_id": "req-123",
"output": {
"urls": {
"get": "https://api.muapi.ai/api/v1/results/req-123"
}
},
},
{"status": "failed", "error": "invalid prompt"},
]
with self.assertRaisesRegex(RuntimeError, "invalid prompt"):
logo_generate._generate_with_muapi(
"logo prompt", "logo.png", "1:1", "muapi-key", "nano-banana"
)
@patch.object(logo_generate, "_json_request")
def test_muapi_requires_creation_result_url(self, json_request):
json_request.return_value = {"request_id": "req-123", "status": "created"}
with self.assertRaisesRegex(RuntimeError, "valid HTTPS result URL"):
logo_generate._generate_with_muapi(
"logo prompt", "logo.png", "1:1", "muapi-key", "nano-banana"
)
json_request.assert_called_once()
@patch.object(logo_generate, "_json_request")
def test_muapi_rejects_invalid_creation_result_url(self, json_request):
json_request.return_value = {
"request_id": "req-123",
"status": "created",
"output": {"urls": {"get": "http://api.muapi.ai/results/req-123"}},
}
with self.assertRaisesRegex(RuntimeError, "valid HTTPS result URL"):
logo_generate._generate_with_muapi(
"logo prompt", "logo.png", "1:1", "muapi-key", "nano-banana"
)
json_request.assert_called_once()
if __name__ == "__main__":
unittest.main()