Single-binary runner for Go scripts with inline dependencies. Like
uv runfor Python orcargo scriptfor Rust — but for Go.
⚠️ Experimental v0.1.0. Tagged but not production-tested yet. The// runlet:depgrammar and CLI surface stay unstable untilv1.0.0— breaking changes between minor versions are allowed and will be called out in CHANGELOG.md. Star/watch to follow.
Write a single .go file, declare its dependencies in a magic
comment, run it — no go.mod, no go.sum, no mkdir mytool && go mod init ceremony.
// runlet:dep github.com/charmbracelet/lipgloss v1.0.0
package main
import (
"fmt"
"github.com/charmbracelet/lipgloss"
)
func main() {
style := lipgloss.NewStyle().
Bold(true).
Foreground(lipgloss.Color("#FAFAFA")).
Background(lipgloss.Color("#7D56F4")).
Padding(0, 1)
fmt.Println(style.Render("hello from a single file"))
}Run it:
runlet hello.goThat's it. runlet parses the dependency comments, synthesises a
throwaway go.mod in a content-addressed cache, downloads deps, and
hands the file to go run.
Other languages closed this gap a while ago:
| Inline-deps runner | |
|---|---|
| Python | uv run (PEP 723 since uv 0.4) — first-class |
| Rust | cargo +nightly script (RFC 3424) — stabilising |
| JS/TS | bunx, deno run — built into the runtime |
| Go | — |
go run works on .go files but only resolves the stdlib. Anything
beyond that needs a full module layout. For one-off scripts —
"parse this log, query that endpoint, format the result" — the module
ceremony is the part that kills the workflow.
gorun (erning/gorun, 2013) was
the first attempt. It predates Go modules and does not handle inline
dependency declarations.
runlet is the modern take: PEP-723-style inline deps, Go-modules
under the hood, a content-addressed cache so the second run of the
same script starts in milliseconds.
go install github.com/goncharovart/runlet/cmd/runlet@latestRequires Go 1.22+. The downloaded runlet binary is the only
prerequisite — it shells out to your existing go toolchain.
A script declares dependencies through line comments at the top of the file:
// runlet:dep <module path> <version>
- Comments anywhere in the file are accepted, but conventionally they go at the top.
<version>follows Go module versioning: a semver tag (v1.2.3), a pseudo-version (v0.0.0-20240101120000-abcdef012345), orlatest(resolved at first run, then frozen in the cache).- Multiple
runlet:deplines are allowed; each one becomes arequiredirective in the synthesisedgo.mod.
Make a script directly executable:
//usr/bin/env runlet "$0" "$@"; exit
// runlet:dep github.com/spf13/cobra v1.9.0
package main
// ...The //usr/bin/env ...; exit trick is the standard Go workaround
for the fact that real #! shebangs break Go's parser. runlet
strips the line before handing the file to go run.
Make it executable with chmod +x script.go and call it as
./script.go.
runlet caches synthesised modules in $XDG_CACHE_HOME/runlet/
(or ~/.cache/runlet/ on Linux/macOS, %LOCALAPPDATA%\runlet\ on
Windows). The cache key is a content hash of the script + sorted
dep list, so:
- Changing the script invalidates only that script's cache entry.
- Two scripts with identical deps reuse the same downloaded modules
(through the standard Go module cache
GOMODCACHE). runlet cache clearwipes everything.
v0.1.0 is shipped. Inside the box: CLI, parser, runner with
content-addressed cache, two shebang formats, an example script, and
a Go-1.22+1.25 × ubuntu/macOS/windows CI matrix.
Roadmap to v0.2.0:
- CLI scaffold, parser, basic runner
- Cache layer (SHA-256 content-addressed, sorted-dep stable)
- Two shebang formats stripped
- First example bundle (
_examples/hello-lipgloss.go) - CI with
-race,golangci-lint, three-OS matrix - #1
runlet cache info/runlet cache clear - #2 Multi-file scripts (
runlet ./scripts/) - #3
runlet --versionviadebug.ReadBuildInfo - #4 Pre-run trust signal on
runlet:depmodules - #5 Homebrew tap + apt repo distribution
Open an issue or a discussion — design feedback on the magic-comment
grammar is especially welcome before v1.0.0 freezes it.
MIT. See LICENSE.