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

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
system_providerSystem package manager only (no download)N/A

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

Common template options

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
)

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"),
    )

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.