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
bash
mkdir crates/vx-providers/mytoolStep 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 --versionThat's it! vx will auto-discover the new provider.
Choosing the Right Template
| 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 |
system_provider | System package manager only (no download) | N/A |
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 |
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:
| 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:
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
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