Skip to content

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:

Quick Start (5 minutes)

Step 1: Create the provider directory

bash
mkdir crates/vx-providers/mytool

Step 2: Write provider.star

For the most common case — a Rust tool distributed via GitHub Releases:

starlark
# 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

bash
vx mytool --version

That'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
TemplateWhen to useAsset example
github_rust_providerRust tools with target triple namingrg-14.1.1-x86_64-unknown-linux-musl.tar.gz
github_go_providerGo tools with goreleaser naminggh_2.67.0_linux_amd64.tar.gz
github_binary_providerSingle binary downloads (no archive)kubectl
github_smart_providerIrregular / unknown naming; wants auto-detect (RFC 0041)auto-scored from release assets
system_providerSystem 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 asset template 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):

PlaceholderExampleDescription
{version}14.1.1Version number
{vversion}v14.1.1With v-prefix
{triple}x86_64-unknown-linux-muslRust target triple
{ext}tar.gz (Unix) / zip (Windows)Archive extension
{exe}`` (Unix) / .exe (Windows)Executable suffix

Go template (github_go_provider):

PlaceholderExampleDescription
{version}2.67.0Version number
{os}linux / darwin / windowsGo GOOS
{arch}amd64 / arm64Go GOARCH
{ext}tar.gz / zipArchive 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:

starlark
_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:

starlark
# 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)

starlark
# 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:

starlark
# 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

starlark
# 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:

starlark
# 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:

FieldTypeDescription
ctx.platform.osstring"windows", "macos", or "linux"
ctx.platform.archstring"x64", "arm64", "x86"
ctx.install_dirstringInstallation directory path
ctx.vx_homestringvx home directory (~/.vx)
ctx.github_tokenstring?GitHub token (if available)

Required and Optional Functions

Required

FunctionSignaturePurpose
fetch_versions(ctx) → descriptorList available versions
store_root(ctx) → stringStorage root path
get_execute_path(ctx, version) → stringPath to executable
environment(ctx, version) → listEnvironment variables

Optional

FunctionSignaturePurpose
download_url(ctx, version) → string?Download URL (return None for unsupported platforms)
install_layout(ctx, version) → dictHow to unpack the archive
post_install(ctx, version) → dict?Actions after installation
deps(ctx, version) → listRuntime dependencies
system_install(ctx, version) → dictSystem package manager fallback
post_extract(ctx, version, install_dir) → listPost-extraction hooks
pre_run(ctx, args, executable) → listPre-run hooks

Platform Constraints

Return None from download_url to indicate a platform is not supported:

starlark
_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 runtimes with runtime_def() (include aliases and version_pattern)
  • [ ] Declare permissions (usually github_permissions())
  • [ ] Implement or use template for: fetch_versions, download_url, install_layout, environment
  • [ ] Test with vx <runtime> --version
  • [ ] Verify on target platforms (check download_url returns correct URLs or None)

Further Reading

Released under the MIT License.