sh-icon-genie
v0.3.0
Published
Interactive CLI that generates a Phosphor-style SVG icon from a natural-language description — library match first, keyless AI generation fallback. Outputs SVG, a React component, and PNG. Ships a browser build for generating icons client-side.
Maintainers
Readme
sh-icon-genie
Interactive CLI that turns a description into a Phosphor-style SVG icon. It matches an existing icon first (offline, free) and falls back to keyless AI generation for anything novel. Outputs SVG, a React component, and a PNG.
npx sh-icon-genie # interactive
npx sh-icon-genie "gold bars" # or describe inlineHow it works
Describe an icon → pick from the closest of 1,500+ bundled icons, reword, or ✨ generate a new one. Generation asks the model for SVG path data (JSON), which is validated, sanitized, and optimized into a viewBox="0 0 256 256", fill="currentColor" icon. Then choose outputs, a name, and a directory.
AI generation
The match step needs no key and is always free/offline. For novel icons, generation works out of the box with no API key — it calls a hosted proxy backed by Cloudflare Workers AI (free):
npx sh-icon-genie "a rocket ship" # pick "✨ Generate" — no key neededSelf-hosting your own proxy is optional — see proxy/. Override the proxy URL per-run with ICON_GENIE_PROXY_URL.
Free models are weaker at precise SVG path geometry than top-tier paid models — expect rougher icons. The library match (free, high quality) covers existing concepts; AI is the fallback for novel ones.
Output
name.svg— optimized,currentColor, 256 gridName.tsx— React component withsize/colorpropsname.png— 512px (needs optional@resvg/resvg-js)
Framing is baked into the path coordinates rather than applied as a wrapper <g transform>. A stored path is therefore always at scale 1, so a given stroke-width means the same apparent thickness in every icon — which is what lets one drawing be re-rendered at several weights.
Browser build
import the package to generate icons from a web page. The entry point has no Node dependencies and bundles as-is:
import { generateWithKey, parsePastedIcon, buildChatPrompt, toCenterlineSvg } from "sh-icon-genie";
// The user's own OpenAI-compatible key, called straight from the tab.
const icon = await generateWithKey({ model: "gpt-4.1-mini", apiKey }, "a watering can");
icon.paths; // 1-4 centerline `d` strings, framed on the 256 grid
// Or: the user pastes buildChatPrompt(desc) into their own AI chat, then pastes the reply back.
const pasted = parsePastedIcon(replyText, "a watering can");generateWithKey sends the key only to the endpoint inferred from the model name (override with baseURL), and never persists it. How a page stores the key is the page's decision: anything in localStorage is readable by any script on the origin, so a single XSS exposes it — prefer holding it in memory for the session.
A browser page cannot read the session of an AI service the user is signed in to elsewhere. The same-origin policy prevents it by design, and a workaround would be credential theft. buildChatPrompt + parsePastedIcon is the supported way to use a subscription your page never sees.
Safety
Generated SVG is validated (path syntax, path count, coordinate framing) and sanitized (no scripts, external refs, or inline event handlers) before it is written or returned. Trademarked logos are refused. Review generated icons before shipping — AI output varies.
Run the gate on every path, including the browser ones: when a page generates client-side, the model's output reaches the DOM without passing through the proxy. generateWithKey and parsePastedIcon do this for you.
The proxy/ is an open endpoint. It caps tokens, prompt size, and path count, pins the model server-side, and rate-limits per IP through a KV namespace bound as RL — required, not optional: with it unbound the worker fails closed and returns 503, because /v1/contribute-icon opens pull requests with a real token.
Develop
npm install && npm run build # → dist/cli.js + dist/browser.js
node dist/cli.js "a watering can"
npm testLicense
MIT
