svgl
Fetch SVG logos from svgl.app via its public API: search by name, browse categories, download the .svg files (light/dark aware) into your project.
Letter = spirit. If a rule blocks you from reaching the goal it was
written for, the rule is wrong, not the goal. Don't look for a wording
loophole — ask what the rule is protecting, and protect that.
Usage
/svgl <name> [name2 ...] [flags]
/svgl --category <Category> [--limit N] [flags]
/svgl --list-categories
Flags:
--theme light|dark|both — for logos that ship theme variants. Default both (writes -light/-dark files); single-variant logos ignore it.
--out <dir> — output directory. Default ./svgl/.
--wordmark — also download the wordmark variant if the logo has one.
--json — don't download; print matched logos' title / category / website / SVG URL(s).
--limit N — cap results (search and category).
--all — download every match without asking (skip disambiguation).
--force — overwrite existing files (default: skip files already present).
Weaknesses and when NOT to use
- Best-effort catalogue. svgl.app has ~660 logos. If a brand isn't there, the term simply returns no match (reported, not invented). Not a universal logo source.
- Undocumented rate limit. The API has no published limit; large
--all batches may hit HTTP 429. scripts/svgl.sh retries once with backoff per request — for big category dumps, expect it to be slow, and prefer --limit.
- Public logos only. No auth; private/internal brand kits are out of scope.
- svgl API quirk (handled): the API
limit param is ignored when combined with search or /category (it silently returns the unfiltered list). scripts/svgl.sh therefore never sends limit to those endpoints and slices client-side. Don't "fix" this by adding &limit= to a search URL — it breaks the search.
- Literal substring search.
search matches the title as a case-insensitive substring, so nextjs does NOT match Next.js (the dot breaks it) — use next. A no-match query is a clean "not found": svgl signals zero results with HTTP 404, which scripts/svgl.sh normalises to an empty result (so it never surfaces as a scary error).
- Not an SVG optimiser/editor. It downloads files as-is from svgl's CDN.
How to do it wrong vs right
Polymorphic route / category
❌ Wrong: Treat item.route as always a string → curl $(jq -r .route) downloads the literal [object Object] / fails for theme-aware logos like React.
route (and wordmark) is string OR {light, dark}; category is string OR string[]. No flag says which.
✅ Right: Branch on type with jq:
# route URL(s) for the requested theme
jq -r 'if (.route|type)=="object" then .route.light, .route.dark else .route end'
# categories as a flat list
jq -r '(.category | if type=="array" then .[] else . end)'
Disambiguation
❌ Wrong: /svgl react → silently download the first search hit (Preact).
- The user asked for "react"; guessing wastes their time and clutters the repo.
✅ Right: If there's an exact (case-insensitive) title match, take it. Otherwise, if >1 match, surface up to 4 via AskUserQuestion (one per candidate) — or honour --all. The user picks.
Search vs limit
❌ Wrong: svgl.sh builds /?search=react&limit=20 → svgl ignores search, returns 20 random logos.
✅ Right: Fetch /?search=react alone; cap with jq .[:N] client-side (the script's slice). Same for /category.
Roles
scripts/svgl.sh (shared with the Codex variant via symlink) wraps the API. Base URL https://api.svgl.app. Deps: curl, jq (both already required by the plugin).
| Subcommand |
Purpose |
Output |
svgl.sh categories |
list categories |
TSV category<TAB>total, sorted by total |
svgl.sh search <query> [limit] |
search by title (substring, case-insensitive) |
raw JSON array (client-side sliced) |
svgl.sh category <name> [limit] |
logos in a category (name case-sensitive) |
raw JSON array (client-side sliced) |
svgl.sh download <url> <outfile> |
fetch one SVG, validate it's SVG |
saved <outfile> or ERROR: … |
What the skill does (step by step)
Parse args — terms vs flags. Pick the mode: --list-categories / --category / search-terms.
--list-categories → bash scripts/svgl.sh categories → print the table. Done.
--category <C> → resolve <C> against svgl.sh categories case-insensitively (the endpoint is case-sensitive; map e.g. library→Library). If no category matches, report the valid names and stop. Then bash scripts/svgl.sh category <RealName> [N]. With --json → print metadata. Otherwise, unless --all, show how many were found and confirm via AskUserQuestion (Download all N / Pick / Cancel) before a bulk download (a category can be dozens of logos). Then go to step 5 for each chosen item.
Search terms — for each term: bash scripts/svgl.sh search "<term>" [N].
- 0 results → record
not found: <term>, continue.
- Exact case-insensitive title match present → take that one item.
- Else exactly 1 result → take it.
- Else (>1, no exact) →
AskUserQuestion listing up to 4 candidates (title — category); if more, note "refine the query or use --all". With --all, take all matches.
Resolve URLs + filenames for each chosen item:
slug = title lowercased, every run of non-[a-z0-9] → -, trimmed (e.g. Proton Mail→proton-mail, D3.js→d3-js).
route is a string → one file <slug>.svg.
route is {light,dark} → per --theme: both (default) → <slug>-light.svg + <slug>-dark.svg; light/dark → only that one, <slug>-<theme>.svg.
--wordmark and wordmark present → same rules with a -wordmark infix (<slug>-wordmark.svg or <slug>-wordmark-<theme>.svg).
--json → skip download; emit {title, category, url, files:[urls]} per item and stop.
Download — for each (url, filename): if the file exists and no --force, skip (report exists). Else bash scripts/svgl.sh download "<url>" "<out>/<filename>". Default <out> = ./svgl/ (created on demand).
Report — per item: saved <path> / exists <path> / error (<reason>); plus not found: <term> lines and a one-line aggregate (K saved, M skipped, E errors).
Outputs
- SVG files under
--out (default ./svgl/), e.g. svgl/react-light.svg, svgl/react-dark.svg, svgl/vercel.svg.
- Nothing else is written; the skill never edits the source note or
.gitignore. With --json, no files — only a printed report.
- No auto-commit (these are project assets; committing them is the user's call).
Connections to other skills
- Standalone utility — not part of the cleanup→blueprint flow and does not call other skills.
- Pairs naturally with
frontend-design / any UI work that needs brand logos.
Rules
Authority (the user picks the logo)
The reference heuristic — "this is probably the React they meant" — is a proposal, not a verdict. When the query is ambiguous (>1 match, no exact title), the choice is the user's via AskUserQuestion, not a silent first-hit download. The exception is --all (explicit intent) and an exact title match (unambiguous).
Commonality (report the whole truth)
Every requested term ends as saved / exists / error / not found. "Got most of them" hides the gaps; downstream work (or the user's asset folder) then has holes they don't know about.
Prior commitment (handle the polymorphism)
You committed to branching on route/category type. Skipping it ("route is a string") silently corrupts theme-aware logos. Not an optimisation — a correctness bug.
Self-check before delivering the result
- Did every search term resolve to
saved / exists / error / not found — none silently dropped?
- For theme-aware logos, did you branch on
route type and write the right -light/-dark files (not a broken [object Object] fetch)?
- Was an ambiguous query (>1 match, no exact) sent through
AskUserQuestion, not auto-resolved to the first hit?
- Are downloaded files valid SVGs (the script validates
<svg/<?xml; an ERROR: means it wasn't)?
- Did you respect
--out / --theme / --force, and create ./svgl/ only when actually downloading?
If "no" on any item — redo, don't ship.
1---2name: svgl3description: Fetch SVG brand/tech logos from the svgl.app public API into a project — by name, by category, or just listing categories. Downloads the actual `.svg` file(s) locally (handling light/dark theme variants and optional wordmarks), or with `--json` returns metadata/URLs only. Tradeoff: depends on svgl.app's catalogue (~660 logos) and its undocumented rate limit; only public logos. For a single logo you already have a URL for, just curl it. Triggers: "svgl", "/svgl", "get logo", "fetch logo", "svg logo", "download logo", "логотип", "svg иконка", "достань логотип".4---56# svgl78Fetch SVG logos from [svgl.app](https://svgl.app) via its public API: search by name, browse categories, download the `.svg` files (light/dark aware) into your project.910> **Letter = spirit.** If a rule blocks you from reaching the goal it was11> written for, the rule is wrong, not the goal. Don't look for a wording12> loophole — ask what the rule is protecting, and protect that.1314## Usage1516```17/svgl <name> [name2 ...] [flags]18/svgl --category <Category> [--limit N] [flags]19/svgl --list-categories20```2122Flags:23- `--theme light|dark|both` — for logos that ship theme variants. Default **both** (writes `-light`/`-dark` files); single-variant logos ignore it.24- `--out <dir>` — output directory. Default `./svgl/`.25- `--wordmark` — also download the wordmark variant if the logo has one.26- `--json` — don't download; print matched logos' title / category / website / SVG URL(s).27- `--limit N` — cap results (search and category).28- `--all` — download every match without asking (skip disambiguation).29- `--force` — overwrite existing files (default: skip files already present).3031## Weaknesses and when NOT to use3233- **Best-effort catalogue.** svgl.app has ~660 logos. If a brand isn't there, the term simply returns no match (reported, not invented). Not a universal logo source.34- **Undocumented rate limit.** The API has no published limit; large `--all` batches may hit HTTP 429. `scripts/svgl.sh` retries once with backoff per request — for big category dumps, expect it to be slow, and prefer `--limit`.35- **Public logos only.** No auth; private/internal brand kits are out of scope.36- **svgl API quirk (handled):** the API `limit` param is **ignored when combined with `search` or `/category`** (it silently returns the unfiltered list). `scripts/svgl.sh` therefore never sends `limit` to those endpoints and slices client-side. Don't "fix" this by adding `&limit=` to a search URL — it breaks the search.37- **Literal substring search.** `search` matches the title as a case-insensitive substring, so `nextjs` does NOT match `Next.js` (the dot breaks it) — use `next`. A no-match query is a clean "not found": svgl signals zero results with HTTP 404, which `scripts/svgl.sh` normalises to an empty result (so it never surfaces as a scary error).38- **Not an SVG optimiser/editor.** It downloads files as-is from svgl's CDN.3940## How to do it wrong vs right4142### Polymorphic `route` / `category`4344❌ **Wrong:** Treat `item.route` as always a string → `curl $(jq -r .route)` downloads the literal `[object Object]` / fails for theme-aware logos like React.45- `route` (and `wordmark`) is **`string` OR `{light, dark}`**; `category` is **`string` OR `string[]`**. No flag says which.4647✅ **Right:** Branch on type with jq:48```bash49# route URL(s) for the requested theme50jq -r 'if (.route|type)=="object" then .route.light, .route.dark else .route end'51# categories as a flat list52jq -r '(.category | if type=="array" then .[] else . end)'53```5455### Disambiguation5657❌ **Wrong:** `/svgl react` → silently download the first search hit (`Preact`).58- The user asked for "react"; guessing wastes their time and clutters the repo.5960✅ **Right:** If there's an exact (case-insensitive) title match, take it. Otherwise, if >1 match, surface up to 4 via `AskUserQuestion` (one per candidate) — or honour `--all`. The user picks.6162### Search vs limit6364❌ **Wrong:** `svgl.sh` builds `/?search=react&limit=20` → svgl ignores `search`, returns 20 random logos.6566✅ **Right:** Fetch `/?search=react` alone; cap with jq `.[:N]` client-side (the script's `slice`). Same for `/category`.6768## Roles6970`scripts/svgl.sh` (shared with the Codex variant via symlink) wraps the API. Base URL `https://api.svgl.app`. Deps: `curl`, `jq` (both already required by the plugin).7172| Subcommand | Purpose | Output |73|---|---|---|74| `svgl.sh categories` | list categories | TSV `category<TAB>total`, sorted by total |75| `svgl.sh search <query> [limit]` | search by title (substring, case-insensitive) | raw JSON array (client-side sliced) |76| `svgl.sh category <name> [limit]` | logos in a category (name **case-sensitive**) | raw JSON array (client-side sliced) |77| `svgl.sh download <url> <outfile>` | fetch one SVG, validate it's SVG | `saved <outfile>` or `ERROR: …` |7879## What the skill does (step by step)80811. **Parse args** — terms vs flags. Pick the mode: `--list-categories` / `--category` / search-terms.82832. **`--list-categories`** → `bash scripts/svgl.sh categories` → print the table. Done.84853. **`--category <C>`** → resolve `<C>` against `svgl.sh categories` **case-insensitively** (the endpoint is case-sensitive; map e.g. `library`→`Library`). If no category matches, report the valid names and stop. Then `bash scripts/svgl.sh category <RealName> [N]`. With `--json` → print metadata. Otherwise, unless `--all`, show how many were found and confirm via `AskUserQuestion` (Download all N / Pick / Cancel) before a bulk download (a category can be dozens of logos). Then go to step 5 for each chosen item.86874. **Search terms** — for each term: `bash scripts/svgl.sh search "<term>" [N]`.88 - 0 results → record `not found: <term>`, continue.89 - Exact case-insensitive title match present → take that one item.90 - Else exactly 1 result → take it.91 - Else (>1, no exact) → `AskUserQuestion` listing up to 4 candidates (title — category); if more, note "refine the query or use `--all`". With `--all`, take all matches.92935. **Resolve URLs + filenames** for each chosen item:94 - `slug` = `title` lowercased, every run of non-`[a-z0-9]` → `-`, trimmed (e.g. `Proton Mail`→`proton-mail`, `D3.js`→`d3-js`).95 - `route` is a **string** → one file `<slug>.svg`.96 - `route` is **`{light,dark}`** → per `--theme`: `both` (default) → `<slug>-light.svg` + `<slug>-dark.svg`; `light`/`dark` → only that one, `<slug>-<theme>.svg`.97 - `--wordmark` and `wordmark` present → same rules with a `-wordmark` infix (`<slug>-wordmark.svg` or `<slug>-wordmark-<theme>.svg`).98 - `--json` → skip download; emit `{title, category, url, files:[urls]}` per item and stop.991006. **Download** — for each (url, filename): if the file exists and no `--force`, skip (report `exists`). Else `bash scripts/svgl.sh download "<url>" "<out>/<filename>"`. Default `<out>` = `./svgl/` (created on demand).1011027. **Report** — per item: `saved <path>` / `exists <path>` / `error (<reason>)`; plus `not found: <term>` lines and a one-line aggregate (`K saved, M skipped, E errors`).103104## Outputs105106- SVG files under `--out` (default `./svgl/`), e.g. `svgl/react-light.svg`, `svgl/react-dark.svg`, `svgl/vercel.svg`.107- Nothing else is written; the skill never edits the source note or `.gitignore`. With `--json`, no files — only a printed report.108- No auto-commit (these are project assets; committing them is the user's call).109110## Connections to other skills111112- Standalone utility — not part of the cleanup→blueprint flow and **does not call** other skills.113- Pairs naturally with `frontend-design` / any UI work that needs brand logos.114115## Rules116117### Authority (the user picks the logo)118The `reference` heuristic — "this is probably the React they meant" — is a proposal, not a verdict. When the query is ambiguous (>1 match, no exact title), the choice is the user's via `AskUserQuestion`, not a silent first-hit download. The exception is `--all` (explicit intent) and an exact title match (unambiguous).119120### Commonality (report the whole truth)121Every requested term ends as `saved` / `exists` / `error` / `not found`. "Got most of them" hides the gaps; downstream work (or the user's asset folder) then has holes they don't know about.122123### Prior commitment (handle the polymorphism)124You committed to branching on `route`/`category` type. Skipping it ("route is a string") silently corrupts theme-aware logos. Not an optimisation — a correctness bug.125126## Self-check before delivering the result127128- Did every search term resolve to `saved` / `exists` / `error` / `not found` — none silently dropped?129- For theme-aware logos, did you branch on `route` type and write the right `-light`/`-dark` files (not a broken `[object Object]` fetch)?130- Was an ambiguous query (>1 match, no exact) sent through `AskUserQuestion`, not auto-resolved to the first hit?131- Are downloaded files valid SVGs (the script validates `<svg`/`<?xml`; an `ERROR:` means it wasn't)?132- Did you respect `--out` / `--theme` / `--force`, and create `./svgl/` only when actually downloading?133134If "no" on any item — redo, don't ship.