A Spicetify Marketplace package manager for your terminal - discover, install, update, and remove themes, extensions, and CSS snippets using the same sources, fetching logic, and filtering rules as the official Spicetify Marketplace.
Built in Rust. Linux-first; also runs on macOS and Windows.
Single static binary, no runtime dependencies beyond spicetify itself.
- Real marketplace catalogue - GitHub topic search over
spicetify-extensions/spicetify-themes, official blacklist + archived filtering, manifest validation identical to the web app (invalid entries are skipped exactly where the app skips them) - Interactive pager - 10 results per page with keyboard navigation, live
STATUS/ENABLEDcolumns, and digit keys that install/remove/toggle rows in place - Safe by default - every action prints an exact summary of what will be written/deleted before it happens; destructive moves require confirmation
- Lockfile - snapshot your installed set and restore it anywhere with one
command (
spice-pm lock→ zero-argspice-pm install) - Clean reinstalls - theme updates wipe the theme folder and reinstall from scratch (with local-drift detection), so you always match upstream
Linux / MacOS:
curl -fsSL https://raw.githubusercontent.com/KamilWachnicki/spicetify-pm/main/install.sh | bashWindows (PowerShell):
irm https://raw.githubusercontent.com/KamilWachnicki/spicetify-pm/main/install.ps1 | iexBoth scripts accept --version vX.Y.Z / -Version vX.Y.Z to pin a release,
and --dir / -InstallDir to change the target directory. Release assets are
named spice-pm-<tag>-<arch>-<os>.tar.gz|.zip.
Build from source instead:
cargo install --path . # or: cargo build --releaseRequires spicetify to already be set up. The config
directory is discovered the same way the spicetify CLI does it:
SPICETIFY_CONFIG env var → %APPDATA%\spicetify (Windows) /
$XDG_CONFIG_HOME|~/.config + /spicetify (Linux & macOS).
Windows and macOS have not been tested yet.
spice-pm is currently developed and tested on Linux. If you're using Windows or macOS, please give it a try and report any issues you encounter by submitting an issue. This will help improve platform support.
Unauthenticated GitHub API calls are limited to 60/hour. Export a token to raise the limit:
Linux
echo 'export GITHUB_TOKEN="your_token_here"' >> ~/.bashrcWindows
[Environment]::SetEnvironmentVariable("GITHUB_TOKEN", "your_token_here", "User")MacOS
echo 'export GITHUB_TOKEN="your_token_here"' >> ~/.profileGH_TOKEN and SPICEPM_GITHUB_TOKEN are also accepted.
spice-pm search [query] [options]
spice-pm info <user/repo|url>
spice-pm install [<user/repo[#name]|url>] [--lockfile <path>]
spice-pm remove <id|name|file> [--yes]
spice-pm update [target]
spice-pm list [all|themes|extensions|snippets] [--json]
spice-pm lock [--out <path>]
spice-pm snippets search [query]|show|add|remove
spice-pm theme set [name] [scheme] | theme scheme <scheme> | theme current
spice-pm cache path|size|clear
spice-pm self-update [--check] [--yes]
Global flags: --no-cache, --apply, --bypass-admin, -v/-vv logging,
--json on read commands.
spice-pm refuses to run elevated (effective UID 0 on Linux/macOS, an elevated token on Windows) - elevated sessions leave admin-owned files that break spicetify for the regular user later, the same reason the spicetify CLI itself guards this. Override when you genuinely need it:
sudo spice-pm search --bypass-adminspice-pm search --type theme --sort stars # whole themes catalogue
spice-pm search bloom --type theme # substring filter
spice-pm search --json adblock # machine-readable output
spice-pm search --page 1 --sort a-z # single API page
spice-pm search --archived # include archived reposEvery result row is an individual manifest entry - a multi-extension repo
like rxri/spicetify-extensions lists each extension separately, matching how
the marketplace grid renders cards. Repos without any valid manifest are
hidden. Columns: # TITLE TYPE STATUS STARS DESCRIPTION.
On a TTY the list becomes an interactive pager:
| Key | Action |
|---|---|
←/→ or p/n |
page |
g/G |
first/last page |
0–9 |
act on that row (see below) |
q/Esc/Ctrl+C |
quit |
What a digit press does:
- uninstalled extension → install summary → confirm (
proceed with install?) → installs; the pager stays open on the same page so you can keep picking - installed extension → removal summary → confirm removal; toggling both ways is fully reversible from the keyboard
- uninstalled theme → full file-by-file install summary → confirm → installs, colour scheme chosen interactively when several exist; the pager closes after one theme operation
- installed theme → removal summary → confirm removal; deactivates and
clears
current_theme/color_schemeonly if that exact theme is active
Rows already installed show a green ✔ installed status; enabled snippets
show green keys. Colors respect NO_COLOR and disappear when piping.
Piped output and --json always print the complete result set without prompts;
--page N limits fetching to a single GitHub results page.
spice-pm info Comfy-Themes/Spicetify
spice-pm info https://github.com/rxri/spicetify-extensions --jsonShows repo metadata plus every valid manifest (id, authors, branch, tags, download URLs) after the same blacklist check used by install.
spice-pm install rxri/spicetify-extensions#adblockify
spice-pm install Comfy-Themes/Spicetify#Comfy # scheme prompt if needed
spice-pm install https://github.com/someone/some-theme
spice-pm remove rxri/spicetify-extensions#adblockify # fragment match + y/N
spice-pm remove rxri/spicetify-extensions#adblockify --yes # skip confirmation
spice-pm update Comfy-Themes/Spicetify#Comfy # clean-reinstall one item
spice-pm update # everythingspice-pm uses repository-based package references because the Spicetify marketplace is currently poorly moderated and decentralized. Using the repository and item name directly makes package identification more explicit and avoids conflicts, using spice-pm search and it's filters is recommended for a hassle-free installation.
- Extensions land in
<SPICETIFY_CONFIG>/Extensions/<file>and are registered under[AdditionalOptions] extensions. - Themes land in
Themes/<name>/-user.css,color.ini, everyinclude[]file, and the first JS include is bridged totheme.jsso spicetify auto-injects it.current_theme+ your chosencolor_schemeare written to[Setting]. If the theme ships scripts, spice-pm also setsinject_theme_js=1for you. - Theme updates are clean reinstalls: the folder is wiped and rebuilt from upstream, local drift (edits/orphans) is detected and reported, and your previously selected colour scheme is restored when it still exists.
- After mutating commands spice-pm prints
run "spicetify apply"; pass global--applyto run it for you.
spice-pm lock # write <SPICETIFY_CONFIG>/spicepm/spicepm.lock
spice-pm lock --out ~/dotfiles/spicepm.lock
cd ~/dotfiles && spice-pm install # restore everything, schemes includedThe lockfile records each pinned item (kind, id, user/repo, branch, chosen
colour scheme) plus enabled snippet keys. It is auto-refreshed on every
install/remove/update, so it never drifts. Zero-arg install
resolves --lockfile → ./spicepm.lock → error with guidance.
spice-pm snippets search # interactive pager, digits toggle
spice-pm snippets search dancing # filter by substring
spice-pm snippets add "Hamsters Dancing"
spice-pm snippets remove "Hamsters Dancing"
spice-pm snippets show "Sonic Dancing"
spice-pm list snippets # enabled snippet keysEnabled snippets are applied through a generated companion extension
(Extensions/spicepm-snippets.js) that injects them at runtime - the same
mechanism the marketplace app uses, surviving theme switches with zero theme
file pollution. The companion is rebuilt automatically whenever extensions are
installed/removed, and orphaned files in Extensions/ are cleaned up.
spice-pm theme set # pick from installed themes interactively
spice-pm theme set Cattpuccin mocha
spice-pm theme scheme latte
spice-pm theme current --jsonResponses are cached on disk with per-type TTLs (search 10 min, manifests and
repo metadata 24 h, blacklist/snippets 1 h). Expiring entries are revalidated
with their ETag - 304 answers cost no GitHub rate-limit quota. When a
refresh fails (rate limit, offline), a stale cached copy is served with a
warning instead of failing; --no-cache disables all of that. Entries older
than 30 days are pruned automatically.
spice-pm cache path # print the cache directory
spice-pm cache size # entry count + total size
spice-pm cache clearCompares the running version against the latest GitHub release and, when outdated, re-runs the install script pinned to that tag over the current binary in place (the previous binary is restored if anything fails).
spice-pm self-update # confirm + update
spice-pm self-update --yes # skip confirmation
spice-pm self-update --check # compare only; exit 1 when outdated- Identity: every item gets a unique, meaningful id -
{user}/{repo}#{Manifest Name}(e.g.Comfy-Themes/Spicetify#Comfy) that matches the install target syntax; the snippet companion lives at reserved id@spicepm/snippets. - Ledger:
<SPICETIFY_CONFIG>/spicepm/ledger.jsontracks provenance (source, branch, resolved URLs, sha256 per file, config references). This is what powersupdate, exact removals, andSTATUSmarks. - Atomicity: config and ledger writes go through temp-file renames; failed actions leave the previous state intact.
- Rate limits: exhausted quota produces a clear message with the reset time; retries cover transient network/server errors.
- Safety: paths recorded in the ledger cannot escape the spicetify dir; downloads overwrite atomically; two items can't claim the same extension filename.
The official manifest schema has no assets field; spice-pm accepts one on
themes and downloads the whole directory it points to. Files land inside
Themes/<folder>/ keeping the assets directory's own name - so a theme whose
repo layout is catppuccin/user.css + "assets": "catppuccin/assets" gets
Themes/<folder>/user.css next to Themes/<folder>/assets/.... Relative
references like url("assets/fonts/Inter.ttf") in the theme's CSS or scripts
resolve against the theme folder, exactly how spicetify expects theme assets.
{
"name": "My Theme",
"description": "...",
"usercss": "my-theme/user.css",
"include": ["my-theme/theme.script.js"],
"assets": "assets"
}The value may be:
- a repo-relative path (
"assets") - resolved against the item's own repo and branch, or - an absolute GitHub directory URL
(
"https://github.com/<user>/<repo>/tree/<branch>/<dir>").
Rules: subdirectories are included recursively; more than 200 files or more
than 25 MiB in total are refused; assets named user.css, color.ini or
theme.js are rejected (the installer owns those); only inert file types
are allowed - images (png, jpg, jpeg, gif, webp, svg, ico,
bmp, avif), fonts (ttf, otf, woff, woff2, eot), styles
(css, scss, sass, less) and text/data (json, txt, md, ini,
xml, yaml, yml, toml, csv) - so a hostile manifest can never make
spice-pm place scripts or binaries on your disk; the whole listing is
validated before anything is downloaded, and any failure aborts the install
(no half-installed themes). Downloaded files land in the ledger, so remove
cleans them up and update refreshes them. Extensions ignore the field.
Installing (or activating via theme set) a theme with assets also sets
overwrite_assets = 1, alongside the usual inject_css / replace_colors /
inject_theme_js handling.
spice-pm ports the marketplace's remote logic field by field:
| Publishing rule | spice-pm |
|---|---|
Discovery via spicetify-extensions / spicetify-themes topics |
✅ topic search, per_page=100, paginated |
manifest.json in repo root |
✅ fetched from raw + default branch |
| Array manifests (multi-extension repos) | ✅ every entry expanded individually |
| Field requirements/optionality | ✅ mirrors the app's zod schema |
branch override / default fallback |
✅ |
| Authors fallback to repo owner; URL sanitization | ✅ |
http(s) URL support for preview/main/readme/usercss/schemes |
✅ verbatim vs raw-relative resolution |
| Blacklist + archived filtering | ✅ glob semantics identical (* = one path segment) |
Theme schemes |
✅ parsed & offered at install |
Theme include[] scripts |
✅ downloaded and bridged to theme.js; inject_theme_js enabled automatically |
Snippets from resources/snippets.json |
✅ via companion extension |
Custom apps (spicetify-apps) |
⏳ planned milestone |
cargo test # 98 unit tests
cargo clippy --all-targets # zero warnings (pedantic lints, unsafe forbidden)
cargo fmt --check
cargo build --releaseLayout of interest:
| Path | Responsibility |
|---|---|
src/market/ |
marketplace parity: search, manifests (zod-parity validation), blacklist globs, snippet fetch, URL rules |
src/spicetify/ |
spicetify CLI parity: directory layout, config-xpui.ini editing, color.ini parsing |
src/commands/ |
one module per command group + shared pager plumbing |
src/http.rs, src/cache.rs |
client (token, retries, rate-limit reporting) + TTL disk cache |
src/ledger.rs |
installed-state tracking (ids, hashes, provenance) |
Environment: RUST_LOG=-v equivalent via flags; NO_COLOR respected;
SPICETIFY_CACHE overrides the cache dir.
Roadmap: custom apps support (topic:spicetify-apps), shell completions.
MIT