
Gemini Designer
- 203 installs
- 51 repo stars
- Updated July 10, 2026
- oil-oil/gemini-designer
Generate UI layouts, component specs, and visual design directions with Gemini during frontend implementation for web or mobile products.
About
gemini-designer from oil-oil/gemini-designer uses Google Gemini to help agents produce UI layouts, component ideas, and visual design specs during frontend build work for SaaS, mobile, and content products.
- Gemini-powered visual and layout ideation
- UI component and screen design guidance
- Branding-aligned design exploration
- Accelerates frontend design iteration
Gemini Designer by the numbers
- 203 all-time installs (skills.sh)
- Ranked #938 of 1,880 Design & UI/UX skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/oil-oil/gemini-designer --skill gemini-designerAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 203 |
|---|---|
| repo stars | ★ 51 |
| Last updated | July 10, 2026 |
| Repository | oil-oil/gemini-designer ↗ |
What it does
Generate UI layouts, component specs, and visual design directions with Gemini during frontend implementation for web or mobile products.
Files
Gemini Designer — Visual Design Advisor
Use Gemini as an external visual design advisor for taste, direction, hierarchy, rhythm, and UI experience.
Critical rules
- ONLY interact with Gemini through the global
gemini-designerCLI. Do not call the API or provider-specific scripts directly. - When this skill is used, call
gemini-designerbefore giving design imagery, art direction, critique, visual advice, HTML, or SVG output. Do not write those deliverables from the agent's own judgment. - Call
gemini-designerdirectly in the normal path. If the command is missing, install it silently with the bundledscripts/install_cliscript, then retry. - For existing UI files, use
gemini-designer advise. Do not usegemini-designer htmlto refine an existing page. - For broad art direction, use
gemini-designer direction. It may include files as background context. - For design imagery markdown, use
gemini-designer directionand read the generated markdown before responding. - For new standalone HTML/SVG design drafts, use
gemini-designer htmlorgemini-designer svg. - For a single handwritten SVG brand wordmark, lettering mark, signature mark, or logo-like text asset, use
gemini-designer svg. - For a comparison sheet with multiple wordmark candidates, use
gemini-designer html. - If the user provides screenshots, mockups, moodboards, or visual references, include relevant images with
-i / --imagewhen they help Gemini judge visual style, layout, hierarchy, mood, or fidelity. - Gemini is stateless. It does not know the current project, prior conversation, screenshots, local files, design rules, or previous Gemini outputs unless they are included in the current command.
- Do not ask Gemini to review code quality, technical debt, CSS lint, or engineering consistency unless the user explicitly asks. Keep Gemini focused on visual effect, design intent, hierarchy, rhythm, taste, and UI experience.
- Do not ask Gemini to output code patches or diffs for existing files. Use its design advice, then make the actual edits yourself.
- After Gemini returns design advice, design imagery markdown, visual direction, or an HTML mockup, show the output or a concise summary to the user and wait for confirmation before implementing it in project code, unless the user explicitly asked to implement immediately.
- After
htmlorsvgreturns, do not start an extra AI review, visual critique, browser screenshot check, grep/tail completeness check, or refinement loop unless the user explicitly asked for checking or iteration. The CLI performs structural integrity checks before writing generated HTML/SVG. Read enough to know what Gemini returned, then present it to the user. - For ordinary HTML, SVG, or icon requests, run the script ONCE per task. Read the output file and proceed.
- Pass the user's stated requirements and concrete project context. Do not add the agent's own style labels, layout choices, color choices, metaphor choices, or evaluation criteria unless the user explicitly said them.
- Do not pre-design for Gemini. For creative generation, the agent should state the user goal, source material, output format, and hard constraints only. Do not name visual directions, metaphors, layouts, palettes, typography, materials, or animation concepts unless the user explicitly provided them.
- For multiple alternatives, ask Gemini for independent, clearly different options. Do not assign the options names like "dashboard direction", "editorial direction", or "radar direction" unless the user gave those directions.
- The CLI manages its own configuration and authorization. Do not pre-check authorization. If a call fails with
error=not_authorized, report that Gemini Designer is not authorized.
Gemini CLI
Use gemini-designer for every Gemini task.
Normal path: call gemini-designer directly. Do not run install or auth checks before every use.
If the shell reports command not found, resolve /path/to/this-skill to the directory containing this SKILL.md, run /path/to/this-skill/scripts/install_cli silently, then retry the original gemini-designer command. If the installer reports a path_warning, use the printed installed_path for this turn and tell the user that the CLI directory is not on PATH.
If the CLI returns error=not_authorized, stop and tell the user Gemini Designer is not authorized. Do not read, copy, print, or manage API keys.
Each command has its own built-in prompt. Choose the right command and pass the user's task plainly; do not add a cross-command prompt framework, design direction, or extra output rules unless the user explicitly gave them.
Commands:
gemini-designer advise— Use for visual design advice. It may include-fand-ias reference context, and requires a readable markdown file name with-o. Gemini gives concrete implementation-oriented visual suggestions, reuse reminders, and pseudo-code snippets when useful. Use this for small refinements and project-style consistency.gemini-designer direction— Use before implementation when the task needs a stronger idea, art direction, visual metaphor, design imagery markdown, or high-level design direction. Files are optional. Always provide a readable markdown file name with-o.gemini-designer html— Use for a new standalone HTML mockup or concept page. It may include-fand-ias reference context, but do not use it to directly revise an existing project file.gemini-designer svg— Use for a new SVG icon, simple illustration, or single handwritten SVG wordmark. It may include-fand-ias reference context.
When advise needs to judge existing UI, pass complete relevant files by default. Do not summarize, slice, or annotate the file unless the file is too large for the CLI limit or the user asks for a scoped review. Prompt-only advise is fine for general design questions.
For advise and direction, always name the markdown file at call time with -o. Prefer a bare readable filename, such as accounts-filter-advice.md, museon-home-art-direction.md, or pricing-page-design-imagery.md; the CLI saves bare names under .gemini-designer/. Use an explicit path only when a specific directory is required.
For html and svg, pass files with -f when Gemini should reference existing content, design rules, previous mockups, theme tokens, example components, or brand references. Treat those files as context for a new design draft, not as files Gemini will patch in place.
For html, describe the design goal only. Do not ask Gemini to save a file, return a file:// URL, provide a download link, or explain where the file is. The CLI writes the output file; Gemini must return raw complete HTML source only.
For html, do not mention asset or dependency policy by default. Let Gemini decide whether images, fonts, scripts, stylesheets, icon libraries, audio, or video help the design. Only add asset constraints when the user explicitly asks for them, such as fully self-contained, offline-safe, no external resources, or use realistic public images.
For handwritten wordmarks, use svg when the user wants one asset, and use html when the user wants several candidates to compare. Describe the word or words, mood, and any explicit material preference from the user. Do not prescribe neon, metal, sci-fi, or other effect-heavy styles unless the user asked for them. The default direction is readable paper-and-ink SVG lettering with subtle writing animation and tasteful finishing marks when useful.
The built-in direction, html, and svg prompts lightly remind Gemini to avoid generic AI templates and default category stereotypes, keep the subject immediately recognizable through concrete sensory anchors, ground design choices in the product subject and real content, use purposeful contrast when it improves visual communication, spend visual boldness in one important place, and keep surrounding UI quiet.
Context Rules
Gemini receives only the command text, files passed with -f, and images passed with -i. It has no memory across calls.
The CLI places the final user goal after reference files and image manifests so Gemini sees the concrete task last. Agents should keep the task text close to the user's wording instead of repeating constraints in several places.
When asking about an existing UI, pass the smallest complete set of files needed for the visual judgment:
- The target file or component being judged
- The project design guide or style reference, if one exists in the workspace
- Related CSS/theme/token files when they materially affect the visual result
- Nearby component files only when they define visible structure or reused UI patterns
- Screenshots, mockups, moodboards, reference images, or exported previews when the user's visual question depends on what the UI looks like
Prefer complete files over excerpts. Use multiple -f flags:
gemini-designer advise "判断这个页面是否符合项目现有视觉风格,并给出具体优化建议" \
-f ./design.html \
-f ./src/styles/tokens.css \
-f ./src/components/Button.tsx \
-o design-page-advice.mdUse -i for image context:
gemini-designer advise "结合截图给这个页面提视觉设计建议" \
-f ./design.html \
-i ./screenshots/current.png \
-o current-page-screenshot-advice.md
gemini-designer direction "参考这张图,生成设计意象 markdown" \
-i ./references/moodboard.png \
-o product-design-imagery.mdWhen images are passed with -i, the CLI may internally send an optimized WebP version to Gemini to reduce request size while preserving readable UI detail. Agents should still pass the original screenshot or reference image path.
Do not pass unrelated source files, build output, dependency folders, logs, or implementation details that do not affect the visual result. If the needed context is too large, choose representative design-system files and say in the task text what is missing.
If Gemini's advise output says it needs more context, do not treat that as final advice. Gather the requested files or information when available, then run gemini-designer advise again with the added -f inputs. If the requested context cannot be found, tell the user exactly what is missing and ask for it.
The built-in advise prompt asks Gemini to:
- stay independent and avoid flattery
- focus on visual effect, design intent, information hierarchy, reading rhythm, atmosphere, UI finish, and user feeling
- avoid turning the response into code review
- keep the answer concrete and actionable enough for implementation
- explain exactly where to change, how to change it, and why the visual result improves
- include pseudo-code, CSS, or JSX snippets when useful, with enough length to explain the change, without outputting a full file
- remind the implementing agent to prefer existing components, selectors, classes, tokens, variables, layout patterns, and interaction patterns
- name reusable components or tokens only when they are visible in the provided files; otherwise do not invent project-specific names
- include a short "do not change" section when there are concrete areas, tokens, components, visual traits, copy, or states that should be preserved
- avoid suggesting a new design system, unrelated components, or decorative additions when existing patterns can be reused
- avoid outputting full HTML
- ask for missing context instead of guessing when a reliable visual judgment is not possible
Useful commands:
gemini-designer advise "给这个页面提视觉设计建议" -f ./design.html -o design-page-advice.md
gemini-designer advise "结合截图给这个页面提视觉设计建议" -f ./design.html -i ./screenshots/current.png -o page-screenshot-advice.md
gemini-designer direction "给这个产品生成设计意象 markdown" -o product-design-imagery.md
gemini-designer direction "基于这个页面生成设计意象 markdown" -f ./design.html -o page-design-imagery.md
gemini-designer html "基于这个页面生成一个完整、全新的落地页方案。保留产品信息,由你独立判断视觉方向,不要复刻原稿。" -f ./design.html -o ./designs/page.html
gemini-designer html "基于这个页面生成第 1 个完整落地页方案。保留产品信息,由你独立判断视觉方向。后续方案需要彼此明显不同。" -f ./design.html -o ./designs/page-option-1.html
gemini-designer svg "为 Museon 生成一个手写 SVG 字标,气质像策展笔记标题" -o museon-wordmark.svg
gemini-designer html "展示 Museon、Mel、Signal 三个手写 SVG 字标候选,默认偏纸面墨迹和真实笔触" -o wordmark-candidates.html
gemini-designer svg "生成一个设置图标" -f ./brand.md -o ./icons/settings.svgRead the output file before acting. Apply only the suggestions that fit the project.
advise and direction outputs include a final 原始提示词 section. It records the task text and readable paths for referenced files or images, without copying the full file contents into the appendix.
For advisory outputs (advise and direction) and HTML mockups from Gemini, do not immediately edit project files. Present Gemini's output or a concise summary, ask the user to choose or confirm the direction, then implement only the confirmed parts. SVG icon output can be saved directly when the user's request is only to create the asset. Do not run a self-review pass just because an HTML or SVG file was created.
The script prints on success:
output_path=<path to output file>For html and svg, the script also prints:
integrity=passedRead the file at output_path to get Gemini's response.
On failure, the CLI prints stable fields:
error=<code>
message=<short explanation>
hint=<next step>Follow the hint when it is actionable. If error=not_authorized, stop and tell the user Gemini Designer is not authorized.
For generated HTML/SVG, treat output_path plus integrity=passed as the normal completeness signal. Do not duplicate CLI checks in the agent unless the user asks for additional browser or visual verification.
Output types
gemini-designer html— Self-contained complete HTML source with inline CSS. Ready to open in browser. The CLI rejects incomplete HTML, missing head/body, file URLs, markdown fences, lorem ipsum, and non-HTML wrapper output.gemini-designer svg— Clean SVG code for icons, simple illustrations, or single handwritten wordmarks. The CLI rejects invalid XML, wrapper text, markdown fences, and local file URLs. Can be saved directly or embedded in HTML/React.gemini-designer adviseandgemini-designer direction— Markdown output.
Configuration
- The global CLI reads
~/.config/gemini-designer/config.toml. - Image optimization defaults to WebP when supported by the local CLI environment.
- Agents should not read, copy, or manage API keys.
- Do not check authorization in the normal path. Use
gemini-designer auth statusonly when explicitly debugging authorization.
When to use
- Need Gemini to inspect existing HTML/CSS/TSX and give visual design advice
- Need a concise visual optimization plan based on one or more local files
- Need a visual reference or HTML mockup for a UI component or page
- Need handwritten SVG wordmark, lettering, signature mark, or logo-like text candidates
- Need SVG icons or simple illustrations
- Need color palette, typography, or layout suggestions
- Need design feedback or critique on an existing design
- Want a quick single-page HTML prototype to show a concept
Workflow
1. Choose the smallest useful Gemini task: advise, direction, html, or svg. 2. Run gemini-designer with a readable -o path and get an output_path before writing the final answer or file. 3. Use the user's wording as the task text whenever possible. Add only factual context needed to identify files, product scope, or constraints the user actually gave. 4. For design imagery markdown or visual direction, call gemini-designer direction. 5. For new HTML/SVG design drafts, call gemini-designer html or gemini-designer svg; include -f or -i when reference context matters. 6. For a single handwritten SVG wordmark, call gemini-designer svg; for several wordmark candidates in one comparison sheet, call gemini-designer html. 7. Read Gemini's output. 8. If advise says more context is needed, gather the requested context and rerun gemini-designer advise once before presenting advice to the user. If the context is unavailable, ask the user for it. 9. When implementing advise output, first look for existing project components, selectors, classes, tokens, variables, and layout patterns to reuse. If Gemini suggests replacing a broad system or inventing unrelated UI, narrow it to existing patterns before editing. 10. If Gemini drifts into code review when the task is visual, rerun with a visual-only goal such as: "只从视觉设计角度判断,不要评论代码规范或工程债。" 11. Present advisory outputs or HTML mockups to the user for confirmation before editing project code, unless the user explicitly asked to implement immediately. 12. Base the final response on Gemini's output. You may summarize, select, or implement useful parts, but do not replace Gemini's design judgment with your own generated design direction.
Tips
- Keep the task prompt close to the user's request.
- If the user did not specify a style, color, font, layout, metaphor, or visual direction, do not invent one in the prompt.
- Do not split multiple Gemini generations by agent-authored themes. Use neutral wording such as "第 1 个独立方案 / 第 2 个独立方案 / 第 3 个独立方案" and ask Gemini to make them clearly different through its own judgment.
- When passing a reference design, say whether Gemini may preserve or should avoid copying it. Do not replace that with your own alternate concepts.
- For HTML mockups, do not mention asset or dependency rules unless the user asks. Unnecessary constraints can weaken Gemini's design judgment.
- Only pass explicit user preferences (e.g. "dark mode", "use blue") when the user actually said so.
- When the task asks for design insight, direction, or an HTML mockup, do not ask Gemini for long explanatory copy. Let Gemini express ideas through visible UI structure, states, examples, hierarchy, and interaction when possible.
- Do not translate "more designed" into visual noise. Strong visual treatment should support key actions, brand memory, and key content entry points; supporting areas should stay quiet, stable, and easy to scan.
- Let Gemini decide how to avoid cliches. Do not add your own list of banned styles, but pass explicit user constraints such as "避免俗套科技感、赛博朋克感" when the user says them.
- Let Gemini actively consider motion, inline SVG, material, and micro-interaction opportunities, but do not prescribe a specific animation or material style unless the user asked for it.
- Do not add your own anti-template checklist to the task prompt. The CLI already gives Gemini light reminders about generic AI aesthetics, memorable visual ideas, restraint, real content, and UI copy.
- Chinese prompts work well — Gemini responds in the same language.
.runtime/
.gemini-designer/
outputs/
__pycache__/
*.pyc
.env
.env.*
!.env.example
config.toml
api_key
Gemini Designer
Gemini Designer is a visual-design advisor skill for agents. It helps with design direction, UI critique, HTML mockups, SVG icons, handwritten wordmarks, and file-based visual feedback.
The skill asks Gemini for visual judgment through the bundled gemini-designer CLI, then lets the main agent decide how to apply the advice in the current workspace.
Install
The easiest way is to give this GitHub repository to an agent such as Codex, Claude Code, or Cursor and ask it to install the skill:
https://github.com/oil-oil/gemini-designerYou can also install it directly from a terminal:
npx skills add oil-oil/gemini-designerAfter installation, agents should use the gemini-designer skill when a task needs external visual design judgment.
Authorization
The CLI reads local configuration from:
~/.config/gemini-designer/config.tomlBy default, the API key is read from:
~/.config/gemini-designer/api_keyDo not commit API keys or local config files. The repository ignores common local secret files, including .env, config.toml, and api_key.
What Agents Should Know
- Gemini is stateless. It only sees the current prompt, files passed with
-f, and images passed with-i. - For existing UI, use
gemini-designer advise. - For broad art direction or design imagery markdown, use
gemini-designer direction. - For new standalone HTML mockups, use
gemini-designer html. - For SVG icons, simple illustrations, and single handwritten wordmarks, use
gemini-designer svg. - Pass complete relevant files when Gemini needs to judge an existing design.
- Pass screenshots or visual references with
-iwhen the visible result matters. - Do not ask Gemini to patch project files directly. Use its advice, then apply the changes in the workspace.
CLI
The skill installs a global command:
gemini-designerTypical examples:
gemini-designer advise "给这个页面提视觉设计建议" -f ./design.html -o design-page-advice.md
gemini-designer direction "给这个产品生成设计意象 markdown" -o product-design-imagery.md
gemini-designer html "生成一个完整的产品页面设计稿" -f ./brief.md -o ./designs/product-page.html
gemini-designer svg "为 Museon 生成一个手写 SVG 字标" -o museon-wordmark.svgBare output filenames are saved under .gemini-designer/ in the current workspace.
Repository Layout
SKILL.md
scripts/gemini-designer
scripts/install_cliSKILL.md tells agents when and how to use Gemini. scripts/install_cli installs the CLI into the user's local bin directory. scripts/gemini-designer is the command agents call.
#!/usr/bin/env python3
"""Gemini Designer CLI."""
from __future__ import annotations
import argparse
import base64
import datetime as dt
import json
import mimetypes
import os
import pathlib
import re
import shutil
import subprocess
import sys
import tempfile
from typing import Iterable
import tomllib
import urllib.error
import urllib.request
import xml.etree.ElementTree as ET
CONFIG_DIR = pathlib.Path(
os.environ.get("GEMINI_DESIGNER_CONFIG_DIR", "~/.config/gemini-designer")
).expanduser()
CONFIG_PATH = CONFIG_DIR / "config.toml"
DEFAULT_API_KEY_FILE = CONFIG_DIR / "api_key"
DEFAULT_BASE_URL = "https://zenmux.ai/api/v1"
DEFAULT_MODEL = "google/gemini-3.1-pro-preview"
DEFAULT_OUTPUT_DIR = pathlib.Path(".gemini-designer")
DEFAULT_MAX_CHARS = 260_000
DEFAULT_MAX_IMAGE_BYTES = 8_000_000
DEFAULT_IMAGE_OPTIMIZE = True
DEFAULT_IMAGE_FORMAT = "webp"
DEFAULT_IMAGE_WEBP_LOSSLESS = True
DEFAULT_IMAGE_WEBP_QUALITY = 86
DEFAULT_IMAGE_MAX_LONG_EDGE = 2200
READABLE_OUTPUT_REQUIRED_COMMANDS = {"advise", "direction"}
HTML_FORBIDDEN_PATTERNS = [
(
"file_url",
re.compile(r"file://", re.IGNORECASE),
"Generated HTML contains a local file:// URL.",
"Retry the command. HTML output should contain source code and normal web/data URLs, not local file links.",
),
(
"markdown_fence",
re.compile(r"```"),
"Generated HTML contains Markdown code fences.",
"Retry the command. HTML output must be raw source only.",
),
]
def fail(error: str, message: str, hint: str | None = None, code: int = 1) -> None:
print(f"error={error}", file=sys.stderr)
print(f"message={message}", file=sys.stderr)
if hint:
print(f"hint={hint}", file=sys.stderr)
raise SystemExit(code)
class DesignerArgumentParser(argparse.ArgumentParser):
def error(self, message: str) -> None:
fail(
"invalid_args",
message,
f"Run `{self.prog} --help` for usage.",
code=2,
)
COMMON_RULES = """General context:
- You are stateless.
- You can only see the user goal, file contents, and image contents included in this request.
- Do not assume you know the project, previous conversation, screenshots, design rules, or files that were not provided.
- Keep independent judgment. Do not flatter the user or add polite praise.
"""
COMMAND_PROMPTS = {
"advise": """Role:
You are an independent visual design advisor.
Task:
Give visual design optimization advice based on the user goal, file contents, and image contents. Focus on the overall impression, design imagery, reading rhythm, information hierarchy, emotional tone, UI finish, and user feeling.
Constraints:
- Do not pick code issues just to sound strict.
- Do not restate the file contents.
- Do not try to prove the current solution is correct.
- Do not output full HTML.
- Do not suggest rewriting the entire page.
- Do not suggest that the main agent create a separate component set, style-token system, layout system, or visual language.
- Do not make CSS hygiene or technical debt the main issue.
- Do not overturn the existing style unless the provided files show that the style cannot support the goal.
- Do not suggest unrelated decorative additions.
- You may not know what components exist in the project. Based only on provided files, generally remind the main agent to look for and reuse existing components, classes, selectors, tokens, variables, and style patterns.
- If reusable objects are clearly visible in the provided files, name them. If they are not visible, do not invent component names.
- If key context is missing, such as an incomplete target file, missing design rules, missing tokens/CSS, missing related visible components, or missing product goals, ask for more context first instead of forcing design advice.
- When information is insufficient, do not guess. State exactly what is missing and what files or information are needed.
- Turn design insight into design expression whenever possible. Prefer UI structure, state examples, visual hierarchy, module relationships, component variants, or interaction demos over explanatory prose.
- Visual design does not need to fill every area. Strong visual treatment should serve key actions, brand memory, and key content entry points; other areas should stay quiet, stable, and easy to scan.
- Look for places where animation, SVG, or material treatment can improve the experience, such as key state feedback, brand memory, content entry points, or complex relationship expression. If it would only decorate, disrupt scanning, or add maintenance burden, do not suggest it.
- Keep the answer concrete and easy to implement.
If key context is missing, output only:
1. Needed context: explain why reliable judgment is not possible.
2. Suggested files or information: explain why each item is needed.
3. Rerun advice: give an example command using `gemini-designer advise ... -f ...` or `-i ...`.
Output format:
1. Issues with the largest visual impact: order by importance, and explain evidence and impact.
2. Actionable changes: each item should include when useful:
- Location: reference a locatable section, component, copy, selector, class, token, or screenshot area.
- Reuse reminder: remind the main agent to first look for and reuse existing components, classes, selectors, tokens, variables, or style patterns; name reusable objects only when they are clearly visible in the files.
- Change: provide concrete values, hierarchy, spacing, colors, type, shadow, border, state, or layout guidance.
- Pseudocode: when useful, include pseudocode, a CSS snippet, or a JSX structure snippet to explain the change; use enough length to make the change clear, but do not output a full file.
- Visual benefit: explain how the change improves impression, hierarchy, rhythm, or consistency.
- Risk: point out possible visual or interaction risks.
3. Do not change: only list specific areas, components, tokens, copy, visual traits, or interaction states that should be preserved, and explain why. Do not repeat broad design-direction summaries.
""",
"direction": """Role:
You are a visual design director.
Task:
Generate high-quality design imagery markdown as a visual design director. Focus on product transformation relationships, industry cliches to avoid, implementable visual metaphors, materiality, composition, motion, and typographic character.
Constraints:
- Do not output full HTML.
- Do not write code.
- Ground the design direction in the product subject, user scenario, real content, page goal, and provided visual signals. Do not apply generic AI templates.
- Avoid default category stereotypes and generic AI cliches. Do not default to conventional tech, cyberpunk, neon, dashboard, or SaaS-template aesthetics unless the subject, audience, or provided references clearly support them.
- Keep the subject immediately recognizable. When the subject depends on living beings, places, objects, crowds, scale, texture, emotion, or sensory experience, preserve enough concrete visual anchors through imagery, illustration, SVG, content examples, UI states, material, or motion so users can identify the subject without reading explanatory copy.
- Write less conceptual explanation and more design expression that an implementer can directly turn into visuals.
- Choose the design dimensions that truly affect this task, such as typographic character, color relationships, spatial rhythm, motion necessity, material detail, component character, and copy tone. You do not need to expand every dimension.
- Each direction should have an explainable memorable idea: state what users will remember and why it belongs to this product or scenario.
- Use purposeful contrast when it strengthens the idea, such as quiet versus intense areas, dense versus open rhythm, tactile versus flat material, intimate versus public scale, or static versus motion. The contrast should create surprise and clarity, not visual noise.
- Actively consider animation, SVG, materiality, composition, and component character as design-expression tools, especially for key actions, brand memory, key content entry points, state feedback, and complex relationship expression.
- Explain where they should appear, what they express, how to implement them with restraint, and which areas they should not spread into.
- Carry the judgment through first-screen structure, module relationships, state examples, visual hierarchy, materiality, composition, motion, and component character.
- Control visual-intensity distribution. Strong visual treatment belongs only on key actions, brand memory, and key content entry points; supporting areas should stay quiet, stable, and easy to scan.
- Do not add unnecessary promotional copy, explanatory copy, or slogans. Keep only short labels, headings, buttons, data, and state messages that a real interface may need.
- Treat copy as interface material. Headings, buttons, labels, errors, empty states, and success states should help users understand and act, avoiding empty slogans.
- If product information, audience, page goal, or reference files are missing, state what is missing and which visual judgments those missing inputs affect.
Output format:
1. Core visual judgment: explain the key design judgment.
2. Cliches to avoid: list directions that would weaken distinctiveness.
3. Candidate visual directions: each should include metaphor, subject, materiality, composition, motion, typographic character, why it fits, and major risks.
4. Recommended direction: recommend the best-fitting direction and explain why.
""",
"html": """Role:
You are a UI and web visual designer.
Task:
Create a self-contained HTML design draft. Focus on clear information hierarchy, distinctive visual imagery, complete interaction states, and mature UI finish.
Output requirements:
- Output one complete HTML file. It must start with <!doctype html> or <html and end with </html>.
- Put CSS in <style> and JS in <script>.
- Use realistic placeholder content. Do not use lorem ipsum.
- Ground the design direction in the user goal, product subject, real content, and reference files. Do not apply generic AI templates.
- Avoid default category stereotypes and generic AI cliches. Do not default to conventional tech, cyberpunk, neon, dashboard, or SaaS-template aesthetics unless the subject, audience, or provided references clearly support them.
- Keep the subject immediately recognizable. When the subject depends on living beings, places, objects, crowds, scale, texture, emotion, or sensory experience, preserve enough concrete visual anchors through imagery, illustration, SVG, content examples, UI states, material, or motion so users can identify the subject without reading explanatory copy.
- Design one restrained but clear memorable idea, and make it serve a key action, brand memory, or key content entry point. Do not make every module the protagonist.
- Use purposeful contrast when it strengthens the design, such as quiet versus intense areas, dense versus open rhythm, tactile versus flat material, intimate versus public scale, or static versus motion. The contrast should create surprise and clarity while the whole page remains coherent and readable.
- Express functionality and design intent through real UI, state examples, data examples, component variants, and simple interaction demos whenever possible. Do not rely on explanatory paragraphs.
- Actively use inline SVG, CSS animation, transitions, micro-interactions, and material feel to express brand memory, state feedback, key content entry points, or complex relationships, but do not make every module animated, glowing, or textured.
- Animation should be short, light, and perceptible. Prefer animation for real UI states such as hover, focus, switching, expanding, progress, selection, empty states, and success states.
- Materiality should support hierarchy and tactility, such as paper, glass, metal, fabric, ink, gloss, or noise texture. Use it only in key areas and keep supporting areas clean.
- Keep page copy restrained. Include only headings, labels, buttons, data, states, and short hints the interface truly needs.
- Copy should help users understand and act. Buttons, labels, errors, empty states, and success states should be specific, natural, and consistent.
- Distribute visual intensity with restraint. Use strong visual treatment for key actions, brand memory, and key content entry points; keep lists, forms, explanations, and supporting information quiet, stable, and easy to scan.
- Avoid turning the page into a design-spec document, long explanatory page, or marketing-copy stack.
- Output raw HTML source only. The first non-whitespace character must be <.
- Do not output Markdown code fences, explanatory text, file paths, file:// URLs, download links, or "file created" messages.
- Do not put any explanatory text before or after the HTML.
""",
"svg": """Role:
You are an SVG icon, simple illustration, and handwritten wordmark designer.
Task:
Create a clean, expressive SVG icon, simple illustration, or handwritten brand wordmark.
Output requirements:
- The SVG must include a reasonable viewBox.
- The structure should be clear and suitable for direct saving or embedding in a page.
- For handwritten wordmarks, create a one-off SVG brand mark, not a full font system.
- For handwritten wordmarks, prefer hand-shaped SVG paths over normal text. The lettering must remain readable.
- Default handwritten wordmarks to paper-and-ink tactility: pen pressure, connected strokes, ink edges, slight bleed, paper friction, pencil or soft marker feel.
- Small finishing marks are allowed when useful, such as a dot, underline, stamp, registration tick, ink pool, or delayed accent. Keep them subordinate to the wordmark.
- If animation is useful, keep it inside the SVG with lightweight <style> animation, and make it feel like real writing: start, travel, pressure, release, and a restrained finishing beat.
- Avoid neon, glow, metallic, sci-fi, hard vector grid, and effect-logo treatments for wordmarks unless the user explicitly asks for that direction.
- SVG is XML. Escape literal ampersands as & or avoid them, including inside <style> comments and visible text.
- Output SVG code only.
""",
}
OUTPUT_TYPES = {
"html": "html",
"svg": "svg",
}
def parse_args(argv: list[str]) -> argparse.Namespace:
parser = DesignerArgumentParser(
prog="gemini-designer",
description="Ask Gemini Designer through one stable CLI.",
epilog=(
"Commands: advise = visual advice, optionally with -f; "
"direction = broad art direction, optionally with -f; "
"html = new standalone HTML, optionally with -f; "
"svg = new standalone SVG, optionally with -f; "
"Debug only: gemini-designer auth status shows whether local authorization is configured."
),
)
parser.add_argument(
"command",
choices=sorted(COMMAND_PROMPTS),
help="Design task command.",
)
parser.add_argument(
"task",
nargs="?",
default="Please complete this visual design task.",
help="User task.",
)
parser.add_argument(
"-f",
"--file",
action="append",
default=[],
help="File to include. Can be repeated.",
)
parser.add_argument(
"-i",
"--image",
action="append",
default=[],
help="Image to include as visual context. Can be repeated.",
)
parser.add_argument(
"-o",
"--out",
help="Output name or path. Bare file names are saved under the configured output_dir.",
)
parser.add_argument(
"--max-chars",
type=int,
default=None,
help=f"Maximum total file characters. Default: config or {DEFAULT_MAX_CHARS}.",
)
parser.add_argument(
"--dry-run",
action="store_true",
help="Write the assembled prompt instead of calling Gemini.",
)
return parser.parse_args(argv)
def require_readable_output(args: argparse.Namespace) -> None:
if args.command in READABLE_OUTPUT_REQUIRED_COMMANDS and not args.out:
fail(
"missing_output",
f"`{args.command}` requires -o with a readable markdown file name.",
f"Use: gemini-designer {args.command} \"your task\" -o readable-name.md",
)
def load_config() -> dict[str, object]:
config: dict[str, object] = {
"api_key_file": str(DEFAULT_API_KEY_FILE),
"base_url": DEFAULT_BASE_URL,
"model": DEFAULT_MODEL,
"output_dir": str(DEFAULT_OUTPUT_DIR),
"max_chars": DEFAULT_MAX_CHARS,
"max_image_bytes": DEFAULT_MAX_IMAGE_BYTES,
"image_optimize": DEFAULT_IMAGE_OPTIMIZE,
"image_format": DEFAULT_IMAGE_FORMAT,
"image_webp_lossless": DEFAULT_IMAGE_WEBP_LOSSLESS,
"image_webp_quality": DEFAULT_IMAGE_WEBP_QUALITY,
"image_max_long_edge": DEFAULT_IMAGE_MAX_LONG_EDGE,
}
if CONFIG_PATH.exists():
try:
data = tomllib.loads(CONFIG_PATH.read_text())
except tomllib.TOMLDecodeError as error:
fail(
"invalid_config",
f"Config file is not valid TOML: {CONFIG_PATH}",
f"Fix the TOML syntax. Details: {error}",
)
for key in config:
if key in data:
config[key] = data[key]
return config
def resolve_path(value: object) -> pathlib.Path:
return pathlib.Path(str(value)).expanduser()
def config_bool(config: dict[str, object], key: str) -> bool:
value = config[key]
if isinstance(value, bool):
return value
if isinstance(value, str):
normalized = value.strip().lower()
if normalized in {"true", "1", "yes", "on"}:
return True
if normalized in {"false", "0", "no", "off"}:
return False
fail(
"invalid_config",
f"`{key}` must be a boolean.",
"Fix ~/.config/gemini-designer/config.toml.",
)
def config_int(config: dict[str, object], key: str) -> int:
try:
return int(config[key])
except (TypeError, ValueError):
fail(
"invalid_config",
f"`{key}` must be an integer.",
"Fix ~/.config/gemini-designer/config.toml.",
)
def read_api_key(config: dict[str, object]) -> tuple[pathlib.Path, str | None]:
key_path = resolve_path(config["api_key_file"])
if not key_path.exists():
return key_path, None
key = key_path.read_text().strip()
return key_path, key or None
def print_auth_status() -> int:
config = load_config()
_, key = read_api_key(config)
status = "authorized" if key else "missing"
print(f"status={status}")
return 0 if key else 1
def handle_auth(argv: list[str]) -> int:
parser = DesignerArgumentParser(prog="gemini-designer auth")
parser.add_argument("command", choices=["status"])
args = parser.parse_args(argv)
if args.command == "status":
return print_auth_status()
return 2
def read_file(path_text: str) -> tuple[pathlib.Path, str]:
path = pathlib.Path(path_text).expanduser()
if path_text.startswith("file://"):
path = pathlib.Path(path_text.removeprefix("file://"))
if not path.exists():
fail(
"file_not_found",
f"File does not exist: {path}",
"Check the path, or pass a valid file:// URL.",
)
if not path.is_file():
fail(
"not_a_file",
f"Path is not a file: {path}",
"Pass a text file with -f. Directories are not supported.",
)
try:
return path.resolve(), path.read_text()
except UnicodeDecodeError:
fail(
"not_utf8",
f"File is not UTF-8 text: {path}",
"Pass source files, HTML, CSS, TSX, or markdown text files.",
)
def resolve_input_path(path_text: str) -> pathlib.Path:
path = pathlib.Path(path_text).expanduser()
if path_text.startswith("file://"):
path = pathlib.Path(path_text.removeprefix("file://"))
return path
def image_dimensions(path: pathlib.Path) -> tuple[int, int] | None:
sips = shutil.which("sips")
if not sips:
return None
try:
result = subprocess.run(
[sips, "-g", "pixelWidth", "-g", "pixelHeight", str(path)],
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
text=True,
)
except (OSError, subprocess.CalledProcessError):
return None
width = height = None
for line in result.stdout.splitlines():
stripped = line.strip()
if stripped.startswith("pixelWidth:"):
width = int(stripped.split(":", 1)[1].strip())
elif stripped.startswith("pixelHeight:"):
height = int(stripped.split(":", 1)[1].strip())
if width and height:
return width, height
return None
def resize_for_long_edge(path: pathlib.Path, max_long_edge: int) -> tuple[int, int] | None:
if max_long_edge <= 0:
return None
dimensions = image_dimensions(path)
if not dimensions:
return None
width, height = dimensions
long_edge = max(width, height)
if long_edge <= max_long_edge:
return None
scale = max_long_edge / long_edge
return max(1, round(width * scale)), max(1, round(height * scale))
def optimize_image_to_webp(
path: pathlib.Path,
data: bytes,
mime: str,
config: dict[str, object],
) -> dict[str, object] | None:
if not config_bool(config, "image_optimize"):
return None
if str(config["image_format"]).strip().lower() != "webp":
return None
if mime in {"image/webp", "image/gif"}:
return None
cwebp = shutil.which("cwebp")
if not cwebp:
return None
lossless = config_bool(config, "image_webp_lossless")
quality = config_int(config, "image_webp_quality")
max_long_edge = config_int(config, "image_max_long_edge")
with tempfile.TemporaryDirectory(prefix="gemini-designer-image-") as tmpdir:
out_path = pathlib.Path(tmpdir) / "optimized.webp"
command = [cwebp, "-quiet"]
resize = resize_for_long_edge(path, max_long_edge)
if resize:
command.extend(["-resize", str(resize[0]), str(resize[1])])
if lossless:
command.append("-lossless")
else:
command.extend(["-q", str(quality)])
command.extend([str(path), "-o", str(out_path)])
try:
subprocess.run(
command,
check=True,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
optimized_data = out_path.read_bytes()
except (OSError, subprocess.CalledProcessError):
return None
if len(optimized_data) >= len(data) and not resize:
return None
return {
"mime": "image/webp",
"data": optimized_data,
"optimized": True,
"optimized_format": "webp",
"resized": bool(resize),
}
def read_image(path_text: str, config: dict[str, object]) -> dict[str, object]:
path = resolve_input_path(path_text)
if not path.exists():
fail(
"image_not_found",
f"Image does not exist: {path}",
"Check the path, or pass a valid file:// URL.",
)
if not path.is_file():
fail(
"not_an_image_file",
f"Path is not a file: {path}",
"Pass an image file with -i, such as PNG, JPEG, WebP, or GIF.",
)
mime, _ = mimetypes.guess_type(path.name)
if not mime or not mime.startswith("image/"):
fail(
"unsupported_image",
f"Unsupported image type: {path}",
"Pass PNG, JPEG, WebP, GIF, or another image file with a known extension.",
)
resolved_path = path.resolve()
data = path.read_bytes()
image: dict[str, object] = {
"path": resolved_path,
"source_mime": mime,
"source_bytes": len(data),
"mime": mime,
"data": data,
"optimized": False,
}
optimized = optimize_image_to_webp(resolved_path, data, mime, config)
if optimized:
image.update(optimized)
max_image_bytes = config_int(config, "max_image_bytes")
sent_bytes = len(image["data"])
if sent_bytes > max_image_bytes:
fail(
"image_too_large",
f"Image is too large after optimization: {resolved_path} ({sent_bytes} bytes). Limit: {max_image_bytes} bytes.",
"Use a smaller screenshot, pass fewer images, or increase `max_image_bytes` in the config.",
)
return image
def build_files_text(files: Iterable[tuple[pathlib.Path, str]], max_chars: int) -> str:
parts: list[str] = []
total = 0
file_list = list(files)
for path, content in file_list:
total += len(content)
if total > max_chars:
fail(
"context_too_large",
f"Included files are too large. Limit: {max_chars} chars. Current total: {total} chars.",
"Pass fewer files, increase --max-chars, or choose representative design-system files.",
)
if len(file_list) == 1:
return file_list[0][1]
for path, content in file_list:
parts.append(f"\n--- FILE: {path} ---\n")
parts.append(content)
parts.append("\n")
return "\n".join(parts)
def build_image_manifest(images: Iterable[dict[str, object]]) -> str:
lines = []
for image in images:
source_mime = image.get("source_mime", image["mime"])
source_bytes = int(image.get("source_bytes", len(image["data"])))
sent_bytes = len(image["data"])
if image.get("optimized"):
details = f"{source_mime}, {source_bytes} bytes; sent {image['mime']}, {sent_bytes} bytes"
if image.get("resized"):
details += "; resized"
lines.append(f"- {image['path']} ({details})")
else:
lines.append(f"- {image['path']} ({image['mime']}, {sent_bytes} bytes)")
return "\n".join(lines)
def build_prompt(
args: argparse.Namespace,
max_chars: int,
images: list[dict[str, object]],
) -> tuple[str, list[tuple[pathlib.Path, str]]]:
files = [read_file(path) for path in args.file]
prompt_parts = [
COMMON_RULES.strip(),
COMMAND_PROMPTS[args.command].strip(),
]
if files:
file_content = build_files_text(files, max_chars)
prompt_parts.extend(
[
"Reference and context files:",
"The following files provide context, existing style, design rules, content, or component references. Do not interpret them as files you should directly modify unless the user goal explicitly says so.",
file_content,
]
)
if images:
prompt_parts.extend(["Image context:", build_image_manifest(images)])
prompt_parts.extend(
[
"Final user goal:",
"Use the reference files and images only as context. The following user goal is the task to satisfy:",
args.task.strip(),
]
)
return "\n\n".join(prompt_parts), files
def original_prompt_appendix(
args: argparse.Namespace,
files: list[tuple[pathlib.Path, str]],
images: list[dict[str, object]],
) -> str:
if args.command not in READABLE_OUTPUT_REQUIRED_COMMANDS:
return ""
task = args.task.strip().replace("```", "'''")
lines = [
"",
"---",
"",
"## Original Prompt",
"",
"```text",
task,
"```",
]
if files:
lines.extend(["", "### Referenced Files"])
lines.extend(f"- {path}" for path, _ in files)
if images:
lines.extend(["", "### Referenced Images"])
lines.extend(build_image_manifest(images).splitlines())
return "\n".join(lines) + "\n"
def output_extension(command: str) -> str:
return OUTPUT_TYPES.get(command, "md")
def default_output_path(command: str, output_dir: pathlib.Path) -> pathlib.Path:
timestamp = dt.datetime.now().strftime("%Y%m%d-%H%M%S")
return output_dir / f"{timestamp}-{command}.{output_extension(command)}"
def resolve_output_path(args: argparse.Namespace, output_dir: pathlib.Path) -> pathlib.Path:
if not args.out:
return default_output_path(args.command, output_dir)
requested = pathlib.Path(args.out).expanduser()
has_directory = (
requested.is_absolute()
or "/" in args.out
or (os.altsep is not None and os.altsep in args.out)
)
if has_directory:
return requested
return output_dir / requested
def system_prompt(command: str) -> str:
if command == "html":
return (
"You are an excellent UI and web visual designer. Output only one complete, "
"self-contained raw HTML document. The first non-whitespace character must be <. "
"Do not output Markdown code fences, explanatory text, file paths, file:// URLs, "
"or download links."
)
if command == "svg":
return "You are an excellent icon and simple illustration designer. Output only valid SVG source."
return "You are an independent visual design advisor. Respond in the same language as the user goal."
def strip_code_fence(content: str) -> str:
stripped = content.strip()
fence_match = re.fullmatch(r"```(?:[A-Za-z0-9_-]+)?\s*\n([\s\S]*?)\n```", stripped)
if fence_match:
return fence_match.group(1).strip()
return stripped
def normalize_html_output(content: str, command: str = "html") -> str:
content = strip_code_fence(content)
start_match = re.search(r"(?is)<!doctype\s+html\b|<html\b", content)
end_match = re.search(r"(?is)</html\s*>", content)
if not start_match or not end_match:
fail(
"invalid_generated_output",
"Gemini did not return a complete HTML document.",
f"Retry the command. The {command} command requires raw HTML, not explanations, file URLs, or partial fragments.",
)
html = content[start_match.start() : end_match.end()].strip()
validate_html_integrity(html, command)
return html + "\n"
def normalize_svg_output(content: str) -> str:
content = strip_code_fence(content)
start = content.lower().find("<svg")
end = content.lower().rfind("</svg>")
if start == -1 or end == -1:
fail(
"invalid_generated_output",
"Gemini did not return valid SVG source.",
"Retry the command. The svg command requires raw SVG, not explanations or file URLs.",
)
svg = content[start : end + len("</svg>")].strip()
svg = escape_bare_xml_ampersands(svg)
validate_svg_integrity(svg)
return svg + "\n"
def validate_html_integrity(content: str, command: str) -> None:
stripped = content.strip()
if not stripped.startswith("<"):
fail(
"invalid_generated_output",
"Gemini returned HTML with text before the document.",
f"Retry the command. The {command} command requires raw HTML source only.",
)
if not re.match(r"(?is)\A\s*(<!doctype\s+html\b|<html\b)", stripped):
fail(
"invalid_generated_output",
"Gemini returned HTML that does not start with a doctype or <html>.",
f"Retry the command. The {command} command requires one complete HTML document.",
)
if not re.search(r"(?is)</html\s*>\s*\Z", stripped):
fail(
"invalid_generated_output",
"Gemini returned HTML that does not end with </html>.",
f"Retry the command. The {command} command requires one complete HTML document.",
)
lowered = stripped.lower()
for required in ("<head", "</head>", "<body", "</body>"):
if required not in lowered:
fail(
"invalid_generated_output",
f"Gemini returned incomplete HTML. Missing `{required}`.",
f"Retry the command. The {command} command requires a complete document with head and body.",
)
for error, pattern, message, hint in HTML_FORBIDDEN_PATTERNS:
if pattern.search(stripped):
fail(f"invalid_generated_output_{error}", message, hint)
if re.search(r"(?is)\blorem\s+ipsum\b", stripped):
fail(
"invalid_generated_output_lorem_ipsum",
"Generated HTML contains lorem ipsum placeholder text.",
"Retry the command with a concrete content goal. The html command requires realistic placeholder content.",
)
def validate_svg_integrity(content: str) -> None:
stripped = content.strip()
if not stripped.startswith("<svg"):
fail(
"invalid_generated_output",
"Gemini returned SVG with text before the <svg> root.",
"Retry the command. The svg command requires raw SVG source only.",
)
if "```" in stripped or "file://" in stripped.lower():
fail(
"invalid_generated_output",
"Gemini returned SVG with output wrapper text or a local file URL.",
"Retry the command. The svg command requires raw SVG source only.",
)
validate_svg_xml(stripped)
def escape_bare_xml_ampersands(content: str) -> str:
return re.sub(
r"&(?!#\d+;|#x[0-9A-Fa-f]+;|[A-Za-z][A-Za-z0-9._:-]*;)",
"&",
content,
)
def validate_svg_xml(content: str) -> None:
try:
root = ET.fromstring(content)
except ET.ParseError as error:
fail(
"invalid_generated_output",
f"Gemini returned SVG that is not valid XML: {error}",
"Retry the command. The svg command requires XML-valid SVG source.",
)
if not root.tag.endswith("svg"):
fail(
"invalid_generated_output",
"Gemini returned XML, but the root element is not SVG.",
"Retry the command. The svg command requires an <svg> root element.",
)
def normalize_model_output(content: str, command: str) -> str:
if command not in OUTPUT_TYPES:
return content
if command == "html":
return normalize_html_output(content, command)
if command == "svg":
return normalize_svg_output(content)
return strip_code_fence(content) + "\n"
def image_content_parts(images: list[dict[str, object]]) -> list[dict[str, object]]:
parts = []
for image in images:
encoded = base64.b64encode(image["data"]).decode("ascii")
parts.append(
{
"type": "image_url",
"image_url": {
"url": f"data:{image['mime']};base64,{encoded}",
},
}
)
return parts
def user_message_content(prompt: str, images: list[dict[str, object]]) -> object:
if not images:
return prompt
return [{"type": "text", "text": prompt}, *image_content_parts(images)]
def request_gemini(
prompt: str,
command: str,
config: dict[str, object],
api_key: str,
images: list[dict[str, object]],
) -> str:
base_url = str(config["base_url"]).rstrip("/")
request = urllib.request.Request(
f"{base_url}/chat/completions",
data=json.dumps(
{
"model": str(config["model"]),
"messages": [
{"role": "system", "content": system_prompt(command)},
{"role": "user", "content": user_message_content(prompt, images)},
],
}
).encode("utf-8"),
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
},
method="POST",
)
try:
with urllib.request.urlopen(request, timeout=300) as response:
body = response.read().decode("utf-8")
except urllib.error.HTTPError as error:
detail = error.read().decode("utf-8", errors="replace")
fail(
"api_http_error",
f"Gemini provider returned HTTP {error.code}.",
detail[:1000],
)
except urllib.error.URLError as error:
fail(
"api_request_failed",
f"API request failed: {error.reason}",
"Check network access or provider configuration.",
)
try:
data = json.loads(body)
except json.JSONDecodeError:
fail(
"invalid_api_response",
"Provider returned a response that is not valid JSON.",
body[:1000],
)
content = data.get("choices", [{}])[0].get("message", {}).get("content", "")
if not content:
fail(
"empty_api_response",
"Provider returned an empty response.",
body[:1000],
)
return normalize_model_output(content, command)
def run_gemini(
prompt: str,
args: argparse.Namespace,
out_path: pathlib.Path,
config: dict[str, object],
images: list[dict[str, object]],
files: list[tuple[pathlib.Path, str]],
) -> None:
_, api_key = read_api_key(config)
if not api_key:
fail(
"not_authorized",
"Gemini Designer is not authorized.",
"Run `gemini-designer auth status`. Ask the user to configure authorization outside the agent.",
)
try:
out_path.parent.mkdir(parents=True, exist_ok=True)
except OSError as error:
fail(
"output_dir_failed",
f"Could not create output directory: {out_path.parent}",
str(error),
)
content = request_gemini(prompt, args.command, config, api_key, images)
content += original_prompt_appendix(args, files, images)
try:
out_path.write_text(content)
except OSError as error:
fail(
"output_write_failed",
f"Could not write output file: {out_path}",
str(error),
)
print(f"output_path={out_path}")
if args.command in OUTPUT_TYPES:
print("integrity=passed")
def main(argv: list[str]) -> int:
if argv[:1] == ["auth"]:
return handle_auth(argv[1:])
config = load_config()
args = parse_args(argv)
require_readable_output(args)
try:
max_chars = args.max_chars if args.max_chars is not None else int(config["max_chars"])
except (TypeError, ValueError):
fail(
"invalid_config",
"`max_chars` must be an integer.",
"Fix ~/.config/gemini-designer/config.toml or pass --max-chars.",
)
images = [read_image(path, config) for path in args.image]
prompt, files = build_prompt(args, max_chars, images)
output_dir = resolve_path(config["output_dir"])
out_path = resolve_output_path(args, output_dir)
if args.dry_run:
try:
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_text(prompt)
except OSError as error:
fail(
"output_write_failed",
f"Could not write dry-run prompt file: {out_path}",
str(error),
)
print(f"prompt_path={out_path}")
return 0
run_gemini(prompt, args, out_path, config, images, files)
return 0
if __name__ == "__main__":
raise SystemExit(main(sys.argv[1:]))
#!/usr/bin/env bash
set -euo pipefail
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
share_dir="${GEMINI_DESIGNER_SHARE_DIR:-$HOME/.local/share/gemini-designer}"
bin_dir="${GEMINI_DESIGNER_BIN_DIR:-$HOME/.local/bin}"
config_dir="${GEMINI_DESIGNER_CONFIG_DIR:-$HOME/.config/gemini-designer}"
mkdir -p "$share_dir" "$bin_dir" "$config_dir"
install -m 0755 "$script_dir/gemini-designer" "$share_dir/gemini-designer"
ln -sfn "$share_dir/gemini-designer" "$bin_dir/gemini-designer"
config_path="$config_dir/config.toml"
if [[ ! -f "$config_path" ]]; then
if [[ "$config_dir" == "$HOME/.config/gemini-designer" ]]; then
api_key_file='~/.config/gemini-designer/api_key'
else
api_key_file="$config_dir/api_key"
fi
umask 077
{
printf '%s\n' 'base_url = "https://zenmux.ai/api/v1"'
printf '%s\n' 'model = "google/gemini-3.1-pro-preview"'
printf 'api_key_file = "%s"\n' "$api_key_file"
printf '%s\n' 'output_dir = ".gemini-designer"'
printf '%s\n' 'max_chars = 260000'
printf '%s\n' 'max_image_bytes = 8000000'
printf '%s\n' 'image_optimize = true'
printf '%s\n' 'image_format = "webp"'
printf '%s\n' 'image_webp_lossless = true'
printf '%s\n' 'image_webp_quality = 86'
printf '%s\n' 'image_max_long_edge = 2200'
} > "$config_path"
fi
if [[ -f "$config_dir/api_key" ]]; then
chmod 600 "$config_dir/api_key"
fi
echo "installed_path=$bin_dir/gemini-designer"
if ! command -v gemini-designer >/dev/null 2>&1; then
echo "path_warning=$bin_dir is not on PATH"
fi