15 KiB
description, allowed-tools
| description | allowed-tools |
|---|---|
| Generate an SVG icon from a design mockup using vectosolve vectorization | Read, Write, Edit, Glob, Grep, Bash, Agent, AskUserQuestion, TodoWrite, mcp__vectosolve__vectorize |
Icon - SVG Icon Generation from Design Mockup
You generate production-quality SVG icons for Telegram Desktop by vectorizing design mockup screenshots using the vectosolve MCP service, then post-processing the result to match the Telegram icon format.
Arguments: $ARGUMENTS = "$ARGUMENTS"
If $ARGUMENTS is empty, ask the user to describe the icon they want and paste a cropped screenshot of it.
Overview
The workflow takes a cropped screenshot of an icon from a design mockup (grabbed from the clipboard), vectorizes it via the vectosolve MCP, then post-processes the SVG (recolor to white-on-transparent, restructure to minimal format, set 24x24 output size).
Working directory: .ai/icon_{name}/ with iterations labeled by letter (a/, b/, ...), each containing source.png. Output SVGs are in the icon root: a.svg, b.svg, etc.
Follow-ups are supported: /icon {icon_name} <description> continues from where the previous run left off.
Phase 0: Setup
Record the current time (using date or equivalent) as $START_TIME.
Step 0a: Clipboard grab (MUST be the VERY FIRST action)
If there is an image attached to the user's message:
-
Generate a random 8-character hex string for
HASH(useopenssl rand -hex 4or similar). -
IMMEDIATELY — before any other processing — run this Bash command to save the clipboard image:
HASH=$(openssl rand -hex 4) && if [[ "$OSTYPE" == darwin* ]]; then bash .claude/grab_clipboard.sh ".ai/icon_${HASH}.png"; else powershell -ExecutionPolicy Bypass -File .claude/grab_clipboard.ps1 ".ai/icon_${HASH}.png"; fiOn macOS
.claude/grab_clipboard.shis used; on Windows.claude/grab_clipboard.ps1. Both grab the current clipboard image and save it to the specified path. -
If the command fails (exit 1 / no image on clipboard):
- Tell the user: "Clipboard doesn't contain an image. Please copy the icon area first, then retry." (On macOS: Cmd+Ctrl+Shift+4 to snip to clipboard; on Windows: Win+Shift+S.)
- STOP IMMEDIATELY. Do NOT continue. You cannot use the image pasted in the conversation — it exists only as pixels in the chat, not as a file you can send to vectosolve. The clipboard grab is the ONLY way to get the image to disk. Do not attempt any workaround.
-
Read back the saved
.ai/icon_HASH.pngusing the Read tool. -
Compare it visually with the image pasted in the conversation. They should depict the same thing.
- If they look completely different: delete
.ai/icon_HASH.pngand fail:"The clipboard image doesn't match what you pasted. Please re-copy and retry."
- If they look the same (or close enough): proceed. Store the temp path.
- If they look completely different: delete
If NO image is attached to the message, skip this step entirely.
Step 0b: Fail-fast — verify vectosolve MCP
Check that the mcp__vectosolve__vectorize tool is available by looking at your available tools list. If it is NOT available, fail immediately with:
vectosolve MCP is not configured. Set it up with:
claude mcp add vectosolve --scope user -e VECTOSOLVE_API_KEY=vs_xxx -- npx @vectosolve/mcpThen restart Claude Code.
Step 0c: Follow-up detection
Extract the first word/token from $ARGUMENTS (everything before the first space or newline). Call it FIRST_TOKEN.
Run these TWO commands using the Bash tool, IN PARALLEL:
ls .ai/— to see all existing icon project namesls .ai/icon_{FIRST_TOKEN}/context.md— to check if this specific icon project exists
Evaluate the results:
- If command 2 succeeds (context.md exists): this is a follow-up. The icon name is
FIRST_TOKEN. The follow-up description is everything in$ARGUMENTSafterFIRST_TOKEN. - If command 2 fails (not found): this is a new icon. The full
$ARGUMENTSis the icon description.
Step 0d: New icon setup
-
Parse
$ARGUMENTSto determine:- Icon description: what the icon should depict
- Icon type: default is
menu(24x24 menu/button icon). User may specify otherwise. - Target subfolder:
menu/by default, or another subfolder if specified.
-
Choose an icon file name:
- Lowercase letters and underscores only — NO hyphens
- Match existing naming conventions (check
Telegram/Resources/icons/{subfolder}/) - Must NOT conflict with existing icons
- Must NOT collide with existing
.ai/icon_{name}/directories
-
Create
.ai/icon_{name}/and.ai/icon_{name}/a/. -
Write
.ai/icon_{name}/context.mdwith:## Icon: {icon_name} Type: {menu/other} Target: Telegram/Resources/icons/{subfolder}/{icon_name}.svg ## Original Request {full $ARGUMENTS text} ## Follow-ups (none yet) -
Set
LETTERtoa.
Step 0e: Follow-up setup
- Read
.ai/icon_{name}/context.mdto get the icon type, subfolder, and full history. - Find the latest existing letter folder in
.ai/icon_{name}/(highest letter). - Set
LETTERto the next letter after the latest. - Create
.ai/icon_{name}/{LETTER}/. - Update
.ai/icon_{name}/context.md— append the follow-up description to the## Follow-upssection:### Follow-up (starting at letter {LETTER}) {follow-up description}
Step 0f: Place source image
If a clipboard image was grabbed in Step 0a:
- Copy (or move)
.ai/icon_HASH.png→.ai/icon_{name}/source.png(overwrite if exists — this is always the latest source). - Copy it to
.ai/icon_{name}/{LETTER}/source.png(archive per-iteration source). - Delete the temp
.ai/icon_HASH.pngif it was copied (not moved).
If NO image was grabbed:
- New icon with no image: Ask the user to provide a screenshot. STOP.
- Follow-up with no image: The existing
source.pngin the icon root carries forward. Copy it to.ai/icon_{name}/{LETTER}/source.png. If no source.png exists at all, ask the user for an image.
Step 0g: Verify renderer
Locate the render tool (codegen_style with --render-svg mode):
if [[ "$OSTYPE" == darwin* ]]; then
ls out/Debug/codegen_style
else
ls out/Telegram/codegen/codegen/style/Debug/codegen_style.exe
fi
If missing, build it: cmake --build out --config Debug --target codegen_style
Test on a known good SVG (use the appropriate binary path for the OS):
CODEGEN=$(if [[ "$OSTYPE" == darwin* ]]; then echo out/Debug/codegen_style; else echo out/Telegram/codegen/codegen/style/Debug/codegen_style.exe; fi)
$CODEGEN --render-svg Telegram/Resources/icons/menu/tag_add.svg .ai/icon_{name}/test_render.png 512
If works → delete test render, set RENDER_AVAILABLE = true. If fails → RENDER_AVAILABLE = false.
Phase 1: Vectorize & Post-process
Step 1a: Call vectosolve
Use the mcp__vectosolve__vectorize tool with file_path set to the absolute path of .ai/icon_{name}/{LETTER}/source.png.
If this fails, STOP IMMEDIATELY. Do NOT try to generate the SVG manually or by any other means. Report the error to the user and let them fix the issue (bad API key, no credits, network error, etc.).
Save the returned SVG content to .ai/icon_{name}/{LETTER}/raw_vectosolve.svg.
The MCP tool calls the vectosolve API ($0.20/call). The API key is stored in ~/.claude.json MCP config (never in the repository).
Step 1b: Post-process the SVG
The vectosolve SVG will have colors from the mockup, arbitrary dimensions, and possibly a non-square aspect ratio from a non-square screenshot crop. Post-processing fixes this by adjusting the viewBox — leave path coordinates untouched.
Do NOT transform path coordinates. Vectosolve's paths are correct — the only thing wrong is the framing. All geometry adjustments are done by manipulating the viewBox and the width/height attributes.
Sub-step 1: Read the request and determine parameters
Before touching the SVG, determine these from the user's request and context.md:
- Output size (
OUT_W × OUT_H): default is24px × 24pxfor menu icons. The user may request different dimensions (e.g., 36×36, 48×48, or non-square). Always check the request. - Content padding: default is ~2px equivalent on each side at the output scale (so content fills roughly (OUT_W-4) × (OUT_H-4)). The user may request different padding or edge-to-edge.
- Centering: default is centered both horizontally and vertically. The user may request specific alignment (e.g., "align to bottom").
Sub-step 2: Parse the raw SVG
- Extract the
viewBox:viewBox="VB_X VB_Y VB_W VB_H"(typically0 0 W H). - Identify ALL paths. Classify each:
- Background: a rect or path spanning the full viewBox (first path that's a simple rectangle matching the viewBox bounds). Remove it entirely.
- Content: the actual icon shapes. Keep these, paths unchanged.
- If paths have
transform="translate(TX,TY)"attributes, that's fine — keep them as-is. The viewBox framing will work regardless.
Sub-step 3: Compute the content bounding box
Estimate the bounding box of the content paths (after removing the background). You can either:
- Eyeball it from the path coordinates (look at first/last M commands and extremes of curves)
- Or for precision, write a quick script to parse the paths and find min/max X/Y
Call the result: CX_MIN, CY_MIN, CX_MAX, CY_MAX. Content dimensions: CW = CX_MAX - CX_MIN, CH = CY_MAX - CY_MIN.
Sub-step 4: Compute the new viewBox
The viewBox determines what part of the SVG coordinate space maps to the output rectangle. By expanding the viewBox beyond the content bounds, we add padding. By making the viewBox aspect ratio match the output aspect ratio, we prevent stretching.
- Output aspect ratio:
OUT_AR = OUT_W / OUT_H(for 24×24 this is 1.0). - Padding in SVG coordinates: we want ~2px padding at output scale. The scale factor is
OUT_W / VB_CONTENT_Wapproximately, so padding in SVG coords =2 * (CW / (OUT_W - 4))(or similar — the exact formula depends on which dimension is dominant). Simpler approach: aim for content to occupy ~83% of the viewBox (≈ 20/24), so:PADDED_W = CW / 0.83PADDED_H = CH / 0.83
- Match output aspect ratio: the viewBox aspect ratio must equal
OUT_ARto avoid stretching.- If
PADDED_W / PADDED_H > OUT_AR: width is dominant →VB_W = PADDED_W,VB_H = VB_W / OUT_AR - If
PADDED_W / PADDED_H < OUT_AR: height is dominant →VB_H = PADDED_H,VB_W = VB_H * OUT_AR - If equal:
VB_W = PADDED_W,VB_H = PADDED_H
- If
- Center the content in the new viewBox:
VB_X = CX_MIN - (VB_W - CW) / 2VB_Y = CY_MIN - (VB_H - CH) / 2- (Adjust if the user requested non-centered alignment)
The new viewBox is: viewBox="VB_X VB_Y VB_W VB_H".
Sub-step 5: Recolor to white-on-transparent
- Replace ALL
fillcolor values (anything that isn'tnone) with#FFFFFF. - Remove ALL
strokeandstroke-widthattributes entirely. - Remove
opacityattributes if present.
Sub-step 6: Determine path composition
Look at the icon's visual structure and decide how paths should combine:
- Outlined shape (e.g., circle outline with something inside): combine outer + inner cutout into one
<path>withfill-rule="evenodd". - Separate distinct parts (e.g., magnifying glass + checkmark): keep as separate
<path>elements. - Filled shape with cutout (e.g., filled circle with checkmark punched out): combine into one path with
fill-rule="evenodd".
Sub-step 7: Assemble final SVG
<?xml version="1.0" encoding="UTF-8"?>
<svg width="{OUT_W}px" height="{OUT_H}px" viewBox="{VB_X} {VB_Y} {VB_W} {VB_H}" xmlns="http://www.w3.org/2000/svg">
<g stroke="none" fill="none" fill-rule="evenodd">
<path d="..." fill="#FFFFFF"></path>
</g>
</svg>
width/height= the output size from the request (default24px/24px).viewBox= the computed viewBox from Sub-step 4. The SVG renderer maps this coordinate region to the output size.- Path
dattributes are unchanged from vectosolve output (just background removed, colors replaced). - No
<title>,id,xmlns:xlink,version,class,style, XML comments,<metadata>, orpreserveAspectRatio. - No
<circle>,<rect>,<line>— only<path>.
Write the final SVG to .ai/icon_{name}/{LETTER}.svg.
Step 1c: Render
If RENDER_AVAILABLE:
$CODEGEN --render-svg ".ai/icon_{name}/{LETTER}.svg" ".ai/icon_{name}/render_{LETTER}.png" 512
Read the render to visually verify the result.
Phase 2: Review
After rendering, assess the result:
- Recognizable? The icon should be clearly identifiable as the intended symbol.
- Scale reasonable? Should fill the space appropriately with ~2-3px padding.
- Clean lines? No broken paths, artifacts, or unwanted elements.
- Correct colors? All white on transparent (no leftover colors from the mockup).
If the result looks good → proceed to Phase 3 (Output).
If there are fixable issues (stray element, missed color, etc.) → fix the SVG directly, re-render, and re-check.
If the result is poor (vectosolve couldn't handle the input well) → report to the user and suggest:
- Trying a cleaner/larger crop of the icon
- Providing a different screenshot
- Following up:
/icon {icon_name} <description of what to change>
Phase 3: Output
-
Read the
Target:line from.ai/icon_{name}/context.mdto get the output path. -
Copy the final SVG to that target path (e.g.,
Telegram/Resources/icons/menu/{icon_name}.svg). -
Update
.ai/icon_{name}/context.md— append to the end:## Latest Output Letter: {LETTER} Written to: {target_path} -
Report to the user:
- Final icon file path
- Number of vectosolve calls made (cost at $0.20/call)
- Suggest verifying visually
- Working directory
.ai/icon_{name}/has all iterations - Elapsed time since
$START_TIME(formatXm Ys) - Follow-up:
/icon {icon_name} <description of what to change>
Text-only Follow-ups (no new image)
When a follow-up has no attached image, the user wants to refine the existing SVG based on text feedback. In this case:
- Skip Phase 1 (no vectosolve call needed).
- Read the latest SVG (
.ai/icon_{name}/{prev_letter}.svg). - Read the latest render if available.
- Apply the user's requested changes by editing the SVG directly.
- Save as
.ai/icon_{name}/{LETTER}.svg. - Render, review, and output as normal (Phases 1c → 3).
If the changes are too complex for manual SVG editing, suggest the user provide a new screenshot instead.
Error Handling
- If clipboard grab fails → tell user to re-copy and retry.
- If vectosolve returns an error → report it and suggest a different/cleaner screenshot.
- If vectosolve returns SVG that can't be parsed → save raw output for debugging, report to user.
- If the render helper fails → set
RENDER_AVAILABLE = false, continue with SVG-only review. - If post-processing produces a broken SVG → fall back to the raw vectosolve output and do lighter cleanup.