Skip to main content
Glama
README.md
# appicon

Resolve desktop and brand icons to **local file paths** for Waybar, Rofi, scripts, and other consumers that need an icon file.

[Website](https://bolens.github.io/appicon/) · [Documentation](docs/README.md) · [Releases](https://github.com/bolens/appicon/releases/latest)

```bash
appicon resolve firefox
appicon resolve --json --format png --size 24 "VS Code"
appicon resolve --json firefox discord
appicon resolve --offline some-cached-app
appicon resolve --explain missing-app
appicon prefetch firefox discord
appicon prefetch --from-desktop
appicon prefetch --json --offline firefox
appicon override set my-browser firefox
appicon override suggest my-browser
appicon override list
appicon sources get --json
appicon sources set --file ./sources.json
appicon status
appicon cache stats
appicon cache prune
appicon mcp   # stdio MCP for agents
appicon daemon            # optional user socket daemon
appicon completion bash   # print completion script
appicon man | man -l -    # view man page
```

**Default resolve order:** file → overrides → XDG / `.desktop` → [SVGL](https://svgl.app/). Change the order with `sources.json`, `sources.yaml`, or `--order`. Optional stages include `simple-icons`, `dashboard-icons`, `github`, BYOK (`logo-dev`, `iconify`, `noun-project`), `glyph`, and local packs. See [docs/sources.md](docs/sources.md) and [docs/packs.md](docs/packs.md).

XDG, SVGL (cache-first), local packs, opt-in CDN/github/BYOK/glyph stages, PNG rasterization, `--offline`, `cache prune`, MCP, optional unix-socket daemon (not on Windows), and shell completions are implemented. Deferred ideas: [docs/deferred.md](docs/deferred.md).

**Consumer contract:** exit `0` for a hit, `1` for a miss, and `2` for an error. `resolve --json` has stable fields for a single object or a `{results:[…]}` batch. See [docs/consumer-contract.md](docs/consumer-contract.md) and the [single](docs/resolve-result.schema.json) and [batch](docs/resolve-batch-result.schema.json) schemas. Callers should keep their fallback glyph after a miss. Keep appicon optional so a missing binary cannot break a bar.

**Portability:** Primary target is Linux (XDG, Flatpak/Snap roots, systemd daemon). The local `xdg` stage also discovers macOS `.app` bundle icons from XML `Info.plist` metadata and Windows `.url` shortcuts with an absolute `IconFile`; binary plist, Windows `.lnk`, and embedded EXE icon extraction are not supported. Config/cache fall back to OS user dirs when `XDG_*` are unset. Daemon refuses on Windows (`daemon_supported=false` in `status`).

**PNG note:** `resolve --format png` prefers `resvg` or `rsvg-convert` on `PATH`, otherwise a pure-Go [oksvg](https://github.com/srwiley/oksvg) fallback. Rasterized files are cached under `$XDG_CACHE_HOME/appicon/raster/`. Pixel size defaults to `48` and is capped at **512** (`--size` / MCP `size` values above that are clamped).

**Theme note:** `--theme dark|light`, `APPICON_THEME`, or `GTK_THEME` suffix (`Adwaita:dark`) prefer matching SVGL/CDN and XDG variants (`name-dark` / `name-symbolic` / `name-light`). Icon **theme name** is separate (`APPICON_ICON_THEME`).

**Sources:** `$XDG_CONFIG_HOME/appicon/sources.json` (or `.yaml`) — every stage is an ordered entry. Default without a file is `file → overrides → xdg → svgl`. Opt-in remotes are never enabled by default. BYOK stages take `token_env` / `secret_env` (env var *names* whose values hold secrets — never put keys or secret paths in config).

```bash
appicon sources list
appicon sources get --json
appicon pack install simple-icons   # local clone + register
appicon pack install --name mine --subdir icons https://github.com/org/my-icons.git
appicon resolve --order glyph,svgl,xdg my-app
appicon resolve --order logo-dev,xdg shopify.com   # needs LOGO_DEV_TOKEN
appicon status
```

Example — remaps and a personal pack before path/XDG/SVGL:

```json
{
  "sources": [
    { "type": "overrides" },
    { "type": "pack", "name": "mine", "path": "~/.local/share/appicon/packs/mine" },
    { "type": "file" },
    { "type": "xdg" },
    { "type": "svgl" },
    { "type": "simple-icons" }
  ]
}
```

Local pack paths must be absolute or start with `~/`; working-directory-relative
and named-user paths are rejected.

CDN stages (`simple-icons` / `dashboard-icons`) are separate from **local** `pack install` clones of the same upstreams. Do not point `http-index` at third-party CDNs unless you control the allowlist and accept their terms.

## Overrides

Long-tail query remaps live in `$XDG_CONFIG_HOME/appicon/overrides.json` (or `.yaml`):

```bash
appicon override set my-wm-class firefox
appicon override list --json
appicon override export --format yaml > overrides.yaml
appicon override import --merge --file overrides.yaml
```

## MCP (agents)

Run the same binary as a stdio MCP server — tools call `internal/resolve` (no extra download logic):

```bash
appicon mcp
```

| Tool | Mirrors |
|------|---------|
| `resolve` | `appicon resolve --json` (optional `order`, `explain`, `queries` batch; miss → `path:null`, not IsError) |
| `prefetch` | `appicon prefetch` (optional `order`, `offline`, `theme`, `from_desktop`, `json`) |
| `status` | `appicon status --json` |
| `sources_list` / `sources_get` / `sources_set` | `appicon sources list\|get\|set` |
| `pack_list` / `pack_path` / `pack_add` / `pack_install` / `pack_update` / `pack_install_bundle` | `appicon pack …` (`pack_install`: `recipe` or `url`, plus `name`/`subdir`/`ref`) |
| `cache_stats` / `cache_clear` / `cache_prune` | matching `cache` subcommands |
| `override_list` / `override_get` / `override_set` / `override_rm` / `override_suggest` / `override_export` / `override_import` | `appicon override …` |
| `version` | `appicon version` |

Example Cursor / Claude Desktop snippet:

```json
{
  "mcpServers": {
    "appicon": {
      "command": "appicon",
      "args": ["mcp"]
    }
  }
}
```

Agents should prefer MCP tools over shelling `appicon` when MCP is connected. Call `resolve` / `sources_*` / `pack_*` only — never invent CDN or SVGL URLs in other repos.

## Daemon (optional)

Long-lived resolve over `$XDG_RUNTIME_DIR/appicon.sock` (mode `0600`). Same allowlists/cache as the CLI, including order, explain, and batch. `resolve` / `prefetch` dial the socket when present and fall back in-process (`--local` / `APPICON_NO_DAEMON=1` skips dial).

```bash
appicon daemon                          # foreground
# or user systemd — see contrib/systemd/README.md
systemctl --user enable --now appicon.socket
```

Home Manager (Linux): `programs.appicon.daemon.enable = true`.

## Shell completions

```bash
# bash
eval "$(appicon completion bash)"
# or install: appicon completion bash > ~/.local/share/bash-completion/completions/appicon

# zsh
appicon completion zsh > "${fpath[1]}/_appicon"   # then: compinit

# fish
appicon completion fish > ~/.config/fish/completions/appicon.fish
```

## Man page

```bash
appicon man | man -l -
# or: appicon man > /usr/local/share/man/man1/appicon.1
```

## Install

```bash
ver=v0.3.0
arch=$(uname -m)
case "$arch" in
  x86_64) arch=amd64 ;;
  aarch64|arm64) arch=arm64 ;;
esac
curl -fsSL "https://github.com/bolens/appicon/releases/download/${ver}/appicon_${ver}_linux_${arch}.tar.gz" | tar -xz
install -m 755 appicon ~/.local/bin/appicon
appicon version   # → v0.3.0
```

Checksums: download `SHA256SUMS` (and optionally `SHA256SUMS.sigstore.json`) from the same release.

```bash
# checksums
sha256sum --check SHA256SUMS
# macOS
shasum -a 256 --check SHA256SUMS

# optional cosign keyless verify (Sigstore)
cosign verify-blob \
  --bundle SHA256SUMS.sigstore.json \
  --certificate-identity-regexp '^https://github.com/bolens/appicon/\.github/workflows/release\.yml@refs/tags/v' \
  --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
  SHA256SUMS

# optional GitHub build provenance attestation
gh attestation verify "appicon_${ver}_linux_${arch}.tar.gz" --repo bolens/appicon
```

Or: `bash scripts/ci/verify-release.sh /path/to/downloaded/assets`.

See [SECURITY.md](SECURITY.md) for reporting vulnerabilities and the trust model.

[waybar-config](https://github.com/bolens/waybar-config) pins this via `make install-appicon`.

### AUR (Arch)

Reference PKGBUILDs live under [packaging/aur/](packaging/aur/):

| Package | Tracks |
|---------|--------|
| `appicon` | Tagged source release |
| `appicon-bin` | Prebuilt release tarball |
| `appicon-git` | Latest git commit on `main` |

Fill checksums (except `-git`) and push to aur.archlinux.org when ready.

### Nix

```bash
nix flake lock    # once
nix run github:bolens/appicon -- version
nix build github:bolens/appicon#appicon-bin   # linux prebuilt (like AUR appicon-bin)
nix build github:bolens/appicon#appicon-git   # source + unstable version (like AUR appicon-git)
# local: see nix/README.md for vendorHash + appicon / appicon-bin / appicon-git
```

Home Manager: `programs.appicon.enable = true` via `homeManagerModules.default` (overlay or set `package` to `appicon` / `appicon-bin` / `appicon-git`). Optional `programs.appicon.daemon.enable = true` for the user socket daemon on Linux.

From source:

```bash
git clone https://github.com/bolens/appicon.git
cd appicon
make build
./bin/appicon version
```

## Cache

Remote assets live under `$XDG_CACHE_HOME/appicon` (default `~/.cache/appicon`
on Linux and the OS user cache directory elsewhere). XDG hits return theme
paths directly and are not copied.

Optional query remaps: `$XDG_CONFIG_HOME/appicon/overrides.json` — manage with:

```bash
appicon override set steam_app_12345 "Some Game"
appicon override get steam_app_12345
appicon override list --json
appicon override export --format yaml
appicon override import --merge --file overrides.yaml
appicon override rm steam_app_12345
appicon override path
```

Brand logos from SVGL are third-party marks — cached for personal use; this project does not redistribute a logo pack.

## Examples

Shell-out-only consumers (no SVGL URLs):

```bash
bash examples/rofi-appicon.sh
bash examples/walker-appicon.sh firefox
bash examples/notify-appicon.sh firefox "Hello" "Icon from appicon"
```

## Development

```bash
make check-fast   # go test + vet + gofmt
make check        # + golangci-lint + govulncheck + gitleaks + actionlint + markdownlint + docs crosslinks
make build
```

## Documentation

Canonical map (update the listed source of truth when behavior changes): **[docs/README.md](docs/README.md)**.

| Topic | Doc |
|-------|-----|
| Exit codes / `resolve --json` | [docs/consumer-contract.md](docs/consumer-contract.md), [docs/resolve-result.schema.json](docs/resolve-result.schema.json) |
| Stages / `sources.json` | [docs/sources.md](docs/sources.md) |
| Local packs / recipes | [docs/packs.md](docs/packs.md) |
| Not a backlog | [docs/deferred.md](docs/deferred.md) |
| Security / verify releases | [SECURITY.md](SECURITY.md) |
| Agents | [AGENTS.md](AGENTS.md) |
| Contributing | [CONTRIBUTING.md](CONTRIBUTING.md) |
| Changelog | [CHANGELOG.md](CHANGELOG.md) |
| Release playbook | [docs/releasing.md](docs/releasing.md) |
| Nix / AUR / systemd | [nix/README.md](nix/README.md), [packaging/aur/README.md](packaging/aur/README.md), [contrib/systemd/README.md](contrib/systemd/README.md) |

Release preparation, tagging, verification, packaging refresh, and local
installation: **[docs/releasing.md](docs/releasing.md)**.

## License

MIT for **code** only. See [LICENSE](LICENSE).

### Git hooks

Run `bash scripts/install-git-hooks` once per clone. The pre-commit hook runs fast staged checks; pre-push runs the broader local CI gate.

## Reproducible development

Use the [development environment guide](docs/development-environments.md) for devenv and local Docker, Podman, or Apple container commands. Existing Make targets and Nix packaging remain authoritative.

## License scope and attribution

See [third-party notices](THIRD_PARTY_NOTICES.md) for the project license scope,
retained upstream notices, and dependency or asset exceptions.

Full [dependency license texts](THIRD_PARTY_LICENSES.txt) accompany this source.