Creating a New Provider
This guide walks you through adding a new tool to vx. All providers are defined using Starlark DSL (provider.star) — no Rust code required.
Reference docs:
- provider.star Reference — Complete DSL reference (execution model, context objects, all stdlib modules)
- Starlark Providers — Advanced Guide — Multi-runtime, hooks, system integration
Quick Start (5 minutes)
Step 1: Create the provider directory
mkdir crates/vx-providers/mytoolStep 2: Write provider.star
For the most common case — a Rust tool distributed via GitHub Releases:
# crates/vx-providers/mytool/provider.star
load("@vx//stdlib:provider.star",
"runtime_def", "github_permissions")
load("@vx//stdlib:provider_templates.star",
"github_rust_provider")
# ---------------------------------------------------------------------------
# Provider metadata
# ---------------------------------------------------------------------------
name = "mytool"
description = "My awesome tool - does something useful"
homepage = "https://github.com/owner/mytool"
repository = "https://github.com/owner/mytool"
license = "MIT"
ecosystem = "devtools"
# ---------------------------------------------------------------------------
# Runtime definitions
# ---------------------------------------------------------------------------
runtimes = [
runtime_def("mytool",
aliases = ["mt"],
version_pattern = "mytool \\d+\\.\\d+",
),
]
# ---------------------------------------------------------------------------
# Permissions
# ---------------------------------------------------------------------------
permissions = github_permissions()
# ---------------------------------------------------------------------------
# Provider functions — generated by template
# ---------------------------------------------------------------------------
_p = github_rust_provider("owner", "mytool",
asset = "mytool-{vversion}-{triple}.{ext}",
executable = "mytool",
)
fetch_versions = _p["fetch_versions"]
download_url = _p["download_url"]
install_layout = _p["install_layout"]
store_root = _p["store_root"]
get_execute_path = _p["get_execute_path"]
post_install = _p["post_install"]
environment = _p["environment"]
deps = _p["deps"]Step 3: Test
vx mytool --versionThat's it! vx will auto-discover the new provider.
Choosing the Right Template
Need to add a new tool to vx?
├── Tool distributed via GitHub Releases?
│ ├── Rust target triple naming? → github_rust_provider
│ ├── Go goreleaser naming? → github_go_provider
│ ├── Single binary (no archive)? → github_binary_provider
│ ├── Irregular / unknown naming? → github_smart_provider ← NEW
│ └── Need asset auto-detection? → github_smart_provider
├── System package manager only? → system_provider
└── Custom download source / logic? → Hand-write download_url + install_layout| Template | When to use | Asset example |
|---|---|---|
github_rust_provider | Rust tools with target triple naming | rg-14.1.1-x86_64-unknown-linux-musl.tar.gz |
github_go_provider | Go tools with goreleaser naming | gh_2.67.0_linux_amd64.tar.gz |
github_binary_provider | Single binary downloads (no archive) | kubectl |
github_smart_provider | Irregular / unknown naming; wants auto-detect (RFC 0041) | auto-scored from release assets |
system_provider | System package manager only (no download) | N/A |
When to Use github_smart_provider
The github_smart_provider (RFC 0041) automatically inspects GitHub Release assets and picks the best match for the current platform via scoring:
- Irregular naming: projects like hugo, ffmpeg, dive with non-standard asset naming
- Quick prototyping: you don't know the exact asset template yet
- As a fallback: provide an explicit
assettemplate as a fallback when auto-detect fails - Maintenance reduction: no need to update platform maps when new architectures are added
For tools with stable, well-known naming conventions (goreleaser Go tools, standard Rust triple naming), the explicit templates (github_go_provider, github_rust_provider) are still preferred for determinism and zero API calls.
Template Placeholders
Rust template (github_rust_provider):
| Placeholder | Example | Description |
|---|---|---|
{version} | 14.1.1 | Version number |
{vversion} | v14.1.1 | With v-prefix |
{triple} | x86_64-unknown-linux-musl | Rust target triple |
{ext} | tar.gz (Unix) / zip (Windows) | Archive extension |
{exe} | `` (Unix) / .exe (Windows) | Executable suffix |
Go template (github_go_provider):
| Placeholder | Example | Description |
|---|---|---|
{version} | 2.67.0 | Version number |
{os} | linux / darwin / windows | Go GOOS |
{arch} | amd64 / arm64 | Go GOARCH |
{ext} | tar.gz / zip | Archive extension |
Smart template (github_smart_provider): No placeholders needed — the template automatically inspects the release assets and scores them against the current platform. All standard options (executable, store, tag_prefix, linux_libc) still apply. See RFC 0041 for the scoring algorithm.
Common template options
Rust / Go / Binary templates:
_p = github_rust_provider("owner", "repo",
asset = "tool-{vversion}-{triple}.{ext}",
executable = "tool", # Executable name (default: repo name)
store = "tool", # Store directory name (default: repo name)
tag_prefix = "v", # Tag prefix (default: "v", use "" for no prefix)
linux_libc = "musl", # Linux libc variant: "musl" (default) or "gnu"
strip_prefix = "tool-{vversion}-{triple}", # Archive top-level dir to strip
)Smart template:
# Minimal — just owner/repo, auto-detects everything
_p = github_smart_provider("owner", "repo")
# With fallback template (used when auto-detect fails)
_p = github_smart_provider("owner", "repo",
executable = "tool",
asset = "tool-{vversion}-{triple}.{ext}", # fallback template
strip = "tool-{vversion}-{triple}", # fallback strip_prefix
linux_libc = "musl", # Linux libc preference
)
# Tuning auto-detect behavior
_p = github_smart_provider("owner", "repo",
score_threshold = 50, # Minimum score to accept (0-100, default: 40)
extra_excludes = ["debug", "static"], # Extra filename keywords to exclude
prereleases = True, # Include pre-release versions
)Real-World Examples
Example 1: Rust tool (ripgrep)
# crates/vx-providers/ripgrep/provider.star
load("@vx//stdlib:provider.star",
"github_rust_provider", "runtime_def", "github_permissions")
name = "ripgrep"
description = "ripgrep (rg) - recursively searches directories for a regex pattern"
homepage = "https://github.com/BurntSushi/ripgrep"
repository = "https://github.com/BurntSushi/ripgrep"
license = "MIT OR Unlicense"
ecosystem = "devtools"
aliases = ["rg"]
runtimes = [runtime_def("ripgrep", executable="rg", aliases=["rg"],
version_pattern="ripgrep \\d+")]
permissions = github_permissions()
_p = github_rust_provider(
"BurntSushi", "ripgrep",
asset = "ripgrep-{version}-{triple}.{ext}",
executable = "rg",
store = "ripgrep",
tag_prefix = "", # No v-prefix in tags
strip_prefix = "ripgrep-{version}-{triple}",
)
fetch_versions = _p["fetch_versions"]
download_url = _p["download_url"]
install_layout = _p["install_layout"]
store_root = _p["store_root"]
get_execute_path = _p["get_execute_path"]
post_install = _p["post_install"]
environment = _p["environment"]
deps = _p["deps"]Example 2: Go tool with custom naming (GitHub CLI)
When the asset naming doesn't fit templates, write custom functions:
# crates/vx-providers/gh/provider.star
load("@vx//stdlib:provider.star", "runtime_def", "github_permissions")
load("@vx//stdlib:github.star", "make_fetch_versions", "github_asset_url")
load("@vx//stdlib:env.star", "env_prepend")
name = "gh"
description = "GitHub CLI"
ecosystem = "devtools"
runtimes = [
runtime_def("gh", aliases=["github-cli"], version_pattern="gh version"),
]
permissions = github_permissions(extra_hosts=["objects.githubusercontent.com"])
fetch_versions = make_fetch_versions("cli", "cli")
def _gh_platform(ctx):
"""Map vx platform to gh's naming convention."""
arch_map = {"x64": "amd64", "arm64": "arm64"}
arch_name = arch_map.get(ctx.platform.arch)
if not arch_name:
return None
if ctx.platform.os == "windows":
return ("windows", arch_name, "zip")
elif ctx.platform.os == "macos":
return ("macOS", arch_name, "zip") # Note: capital M
elif ctx.platform.os == "linux":
return ("linux", arch_name, "tar.gz")
return None
def download_url(ctx, version):
platform = _gh_platform(ctx)
if not platform:
return None
os_name, arch_name, ext = platform[0], platform[1], platform[2]
asset = "gh_{}_{}_{}.{}".format(version, os_name, arch_name, ext)
return github_asset_url("cli", "cli", "v" + version, asset)
def install_layout(ctx, version):
if ctx.platform.os == "windows":
return {"type": "archive", "strip_prefix": "",
"executable_paths": ["bin/gh.exe", "gh.exe"]}
platform = _gh_platform(ctx)
if not platform:
return {"type": "archive", "strip_prefix": "", "executable_paths": ["bin/gh"]}
os_name, arch_name, _ = platform[0], platform[1], platform[2]
return {"type": "archive",
"strip_prefix": "gh_{}_{}_{}/".format(version, os_name, arch_name),
"executable_paths": ["bin/gh"]}
def store_root(ctx):
return ctx.vx_home + "/store/gh"
def get_execute_path(ctx, _version):
return ctx.install_dir + ("/gh.exe" if ctx.platform.os == "windows" else "/gh")
def post_install(_ctx, _version):
return None
def environment(ctx, _version):
return [env_prepend("PATH", ctx.install_dir)]
def deps(_ctx, _version):
return []Example 3: System package manager only
# For tools that can only be installed via system package managers
load("@vx//stdlib:provider.star",
"runtime_def", "system_permissions",
"cross_platform_install", "winget_install", "brew_install", "apt_install")
load("@vx//stdlib:provider_templates.star", "system_provider")
name = "mytool"
description = "A system tool"
ecosystem = "system"
runtimes = [runtime_def("mytool")]
permissions = system_permissions()
_p = system_provider("mytool", executable="mytool")
fetch_versions = _p["fetch_versions"]
store_root = _p["store_root"]
get_execute_path = _p["get_execute_path"]
environment = _p["environment"]
def system_install(_ctx, _version):
return cross_platform_install(
windows = winget_install("Publisher.MyTool"),
macos = brew_install("mytool"),
linux = apt_install("mytool"),
)Example 4: Smart provider (hugo)
When a tool has irregular asset naming, github_smart_provider automatically detects the right asset — no platform map needed:
# crates/vx-providers/hugo/provider.star
# Uses github_smart_provider — asset auto-detected from GitHub Release assets.
# No platform map, no manual download_url, no install_layout needed.
load("@vx//stdlib:provider.star",
"runtime_def", "github_permissions")
load("@vx//stdlib:provider_templates.star",
"github_smart_provider")
name = "hugo"
description = "Hugo static site generator"
homepage = "https://gohugo.io"
repository = "https://github.com/gohugoio/hugo"
license = "Apache-2.0"
ecosystem = "devtools"
runtimes = [runtime_def("hugo", aliases=["hugo"],
version_pattern="hugo v\\d+")]
permissions = github_permissions()
# github_smart_provider inspects all release assets and picks the best match
# using the scoring algorithm defined in RFC 0041.
# - For irregular naming: hugo releases use different extensions per platform
# (hugo_0.146.7_windows-amd64.zip, hugo_0.146.7_darwin-universal.tar.gz, etc.)
# - The asset parameter below is a FALLBACK template used only when auto-detect
# fails (e.g. GitHub API unavailable or no asset scores above threshold).
_p = github_smart_provider("gohugoio", "hugo",
executable = "hugo",
asset = "hugo_{version}_{os}-{arch}.{ext}",
strip = "",
)
fetch_versions = _p["fetch_versions"]
download_url = _p["download_url"]
install_layout = _p["install_layout"]
store_root = _p["store_root"]
get_execute_path = _p["get_execute_path"]
post_install = _p["post_install"]
environment = _p["environment"]
deps = _p["deps"]Compare this to the hand-written approach (Example 2): the smart provider eliminates ~40 lines of platform mapping boilerplate.
Context Object (ctx)
All provider functions receive a ctx object:
| Field | Type | Description |
|---|---|---|
ctx.platform.os | string | "windows", "macos", or "linux" |
ctx.platform.arch | string | "x64", "arm64", "x86" |
ctx.install_dir | string | Installation directory path |
ctx.vx_home | string | vx home directory (~/.vx) |
ctx.github_token | string? | GitHub token (if available) |
Required and Optional Functions
Required
| Function | Signature | Purpose |
|---|---|---|
fetch_versions | (ctx) → descriptor | List available versions |
store_root | (ctx) → string | Storage root path |
get_execute_path | (ctx, version) → string | Path to executable |
environment | (ctx, version) → list | Environment variables |
Optional
| Function | Signature | Purpose |
|---|---|---|
download_url | (ctx, version) → string? | Download URL (return None for unsupported platforms) |
install_layout | (ctx, version) → dict | How to unpack the archive |
post_install | (ctx, version) → dict? | Actions after installation |
deps | (ctx, version) → list | Runtime dependencies |
system_install | (ctx, version) → dict | System package manager fallback |
post_extract | (ctx, version, install_dir) → list | Post-extraction hooks |
pre_run | (ctx, args, executable) → list | Pre-run hooks |
Platform Constraints
Return None from download_url to indicate a platform is not supported:
_PLATFORMS = {
"windows/x64": ("win", "x64"),
"macos/arm64": ("darwin", "arm64"),
"linux/x64": ("linux", "x64"),
}
def download_url(ctx, version):
key = "{}/{}".format(ctx.platform.os, ctx.platform.arch)
platform = _PLATFORMS.get(key)
if not platform:
return None # Unsupported platform
os_str, arch_str = platform
return "https://example.com/v{}/tool-{}-{}.tar.gz".format(version, os_str, arch_str)Checklist
- [ ] Create
crates/vx-providers/<name>/provider.star - [ ] Define metadata:
name,description,ecosystem - [ ] Define
runtimeswithruntime_def()(includealiasesandversion_pattern) - [ ] Declare
permissions(usuallygithub_permissions()) - [ ] Implement or use template for:
fetch_versions,download_url,install_layout,environment - [ ] Test with
vx <runtime> --version - [ ] Verify on target platforms (check
download_urlreturns correct URLs orNone)
Further Reading
- provider.star Reference — Complete DSL reference with all stdlib modules
- Starlark Providers — Advanced — Multi-runtime providers, hooks, system integration
- Command Syntax Rules — How commands are parsed and routed