Lets 0.0.63
Lets 0.0.63 is available now.
Important for existing installations: the installer-managed binary now lives at $HOME/.lets/bin/lets, with a symlink from a directory in your PATH. Follow the Upgrade section once to move an older installation safely.
If you are coming from 0.0.55, this release is much more than a collection of fixes.
Lets can now run a config from a URL, load dotenv files, has new checksum configuration, better explain failed dependencies, and teach AI agents to use project tasks.
The result is a task runner that is easier to share, easier to understand when something goes wrong, and more pleasant to use every day.
Highlights
- Migrate an existing installation into the new user-owned layout.
- Configure checksums with one clear, migratable structure.
- Run and refresh shared task workflows from a URL.
- See the complete dependency path when a command fails.
- Install a bundled Agent Skill for compatible coding agents.
Safer installation and upgrades
If you use bash installation script https://lets-cli.org/install.sh, you need to be aware that there is a new filesystem layout.
The new filesystem layout separates the managed binary from the command exposed through PATH.
Previously installation script installed lets to current directory or to -b /path/to/binary.
If you previously installed
letsusing old installation script, see #Upgrade section.
Now, the installer writes the binary to $HOME/.lets/bin/lets. It creates a symlink in the first preferred directory already present in PATH:
$HOME/.local/bin$HOME/bin$HOME/.bin
If none of those directories are available, the installer creates $HOME/.local/bin, places the symlink there, and asks to add that directory to your shell profile.
By default installation script will propmt if user whats installator to update user's profile (.bashrc or .zshrc). If you want to install lets using installation script on CI or in Dockerfile, use LETS_INSTALL_NO_PROMPT=1 to suppress profile updating prompts.
lets self upgrade follows the symlink and replaces the managed target. It refuses common system-managed locations and redirects Homebrew users to the correct package-manager command.
Upgrade
Already have Lets installed? First check the version and the binary currently selected by your PATH:
lets --version
command -v lets
Then use the upgrade path that matches the original installation method.
Homebrew
Keep the installation managed by Homebrew:
brew upgrade lets-cli/tap/lets
Do not use lets self upgrade for a Homebrew installation.
Arch Linux
Keep the installation managed by AUR:
yay -Syu lets-bin
Installer or manually downloaded binary
If you are upgrading from 0.0.55, use the current installer to migrate to the new user-owned layout:
curl -fsSL https://lets-cli.org/install.sh | bash
The old lets --upgrade command replaces the binary at its current path. It does not move an existing 0.0.55 installation into the new layout.
The installer refuses to overwrite a non-Homebrew binary at /usr/local/bin/lets. If it reports that legacy installation, confirm it was not installed by Homebrew, then run:
sudo rm /usr/local/bin/lets
curl -fsSL https://lets-cli.org/install.sh | bash
If /usr/local/bin/lets is managed by Homebrew, leave it in place and use brew upgrade lets-cli/tap/lets instead.
Verify the active binary
Open a new shell after the installer updates your shell profile, then verify the command, symlink, and version:
command -v lets
ls -l "$(command -v lets)"
lets --version
With the default installer settings, the symlink should resolve to $HOME/.lets/bin/lets, and the version should be 0.0.63.
If command -v lets still shows an older custom path, that copy appears earlier in PATH. Remove it or move the new symlink directory earlier, but only after confirming the new binary works.
After this one-time migration, future installer-managed updates are:
lets self upgrade
lets self update
lets self update is a new alias for the same operation.
Prereleases
Prereleases remain opt-in in 0.0.63. Release candidates do not replace the stable Homebrew formula or AUR package. Testers using an installer-managed copy can opt in with:
lets self upgrade --pre
Homebrew users can install the separate formula:
brew install lets-cli/tap/lets-beta
The stable and beta formulas both provide the lets binary, so uninstall one before switching to the other.
See the installation guide for binaries, source builds, CI, and uninstall instructions.
Run config from a URL
Lets can now use an HTTP or HTTPS URL as its main config:
lets -c https://example.com/lets.yaml build
The config is downloaded and cached in ~/.config/lets/remote-configs/. Future runs use the cached copy, while --no-cache forces a refresh:
lets --no-cache -c https://example.com/lets.yaml build
Remote configs make it practical to publish a shared workflow once and invoke it from a repository, CI job, or onboarding guide without first copying a file into place.
Commands run from the directory where you invoke Lets unless they declare work_dir, so the remote workflow can still operate on the current project.
Downloads for remote configs and remote mixins now show progress in an interactive terminal. Cache hits stay quiet, and --no-cache refreshes both kinds of remote file.
Read more in the remote configs and remote mixins documentation.
Load .env files directly
Global and command-level env_file support removes a common layer of shell setup. Files may be required, optional, or selected using values resolved earlier in the config:
shell: bash
env:
TARGET: dev
env_file:
- .env.${TARGET}
- -.env.local
- name: .env.${LETS_OS}
required: true
commands:
serve:
env:
SERVICE: api
env_file:
- .env.${TARGET}.${SERVICE}
- -.env.override
cmd: ./bin/serve
The - prefix marks a file as optional. Paths are resolved relative to the config directory, not the command's work_dir.
The precedence is deliberate: a global env_file overrides global env, a command env_file overrides command env, and command-line options such as -E KEY=value still win at the end.
See the environment reference for the complete order.
Lets also exposes LETS_OS and LETS_ARCH, which makes platform-specific files and commands straightforward:
env_file: .env.${LETS_OS}.${LETS_ARCH}
Checksums are clearer and easier to migrate
Checksum configuration now has one structured shape. Use checksum.files for files or globs, checksum.sh for a revision supplied by a command, and checksum.persist to remember the value between successful runs.
For example, install dependencies only when the lockfile changes:
commands:
build:
checksum:
files:
- package-lock.json
persist: true
cmd: |
if [[ ${LETS_CHECKSUM_CHANGED} == true ]]; then
npm ci
fi
npm run build
On the first run, LETS_CHECKSUM_CHANGED is true. It becomes false until an input changes. Lets saves the new checksum only after the command exits successfully, so a failed setup is retried next time.
Use checksum.sh when a file list is not the right source of truth:
checksum:
sh: git rev-parse HEAD
files and sh are mutually exclusive. File checksums now also honor the command-level work_dir, so relative paths resolve from the same directory as the command.
The previous direct checksum list or map and command-level persist_checksum remain compatible, but are deprecated. Preview an automatic migration, then apply it:
lets self fix --dry-run
lets self fix
self fix updates the main config and local mixins. Remote mixins are left unchanged with a warning because Lets does not own those files. See the checksum reference for named checksums and environment variables.
Failures now show the route to the problem
A failed dependency used to leave you reconstructing the call chain yourself. Lets now prints the full command tree and points to the failing leaf:
ERROR
Failed to run command 'lint'.
Exit status 1.
COMMAND TREE:
└─ deploy
└─ build
└─ lint <-- failed here
Typos are friendlier too:
Unknown command "slef" for "lets"
Did you mean this?
self
Unknown commands now exit with status 2, which distinguishes incorrect CLI usage from a task that ran and failed.
Help and error output received a broader visual cleanup too: spacing is more consistent, long command names stay aligned, and logging uses one predictable lets: prefix.
Keep large command lists readable
The new group directive lets a project turn a long flat help screen into a small menu:
commands:
build:
group: Development
description: Build the application
cmd: npm run build
test:
group: Development
description: Run the test suite
cmd: npm test
deploy:
group: Operations
description: Deploy the application
cmd: ./deploy.sh
Running lets help groups related commands:
COMMANDS:
DEVELOPMENT
build Build the application
test Run the test suite
OPERATIONS
deploy Deploy the application
This is purely organizational: command names and invocation stay unchanged. See the group reference.
Configs can also use custom top-level keys beginning with x-, so YAML anchors can hold reusable data without failing validation. Environment mappings now merge YAML aliases too:
x-default-env: &default-env
LOG_LEVEL: info
REGION: eu-central-1
env:
<<: *default-env
LOG_LEVEL: debug
Unknown top-level keys that do not begin with x- are rejected, catching misspelled directives early.
Personal Lets settings
Lets now allows to configure personal preferences:
~/.config/lets/config.yaml
Find or open that file without remembering the path:
lets self config path
lets self config edit
The settings file controls color, background update notifications, and the help/error theme:
no_color: false
theme: synthwave
upgrade_notify: true
Available themes are default, ansi, and synthwave. Environment variables continue to take precedence over the settings file.
Interactive sessions can also notify you about new Lets versions in the background. Set LETS_CHECK_UPDATE=1 or upgrade_notify: false when you prefer a quiet CLI.
The complete list is in the new settings reference.
Quality-of-life improvements
Many smaller changes are aimed at reducing daily friction:
lets self docopens the online documentation in your browser.lets self updateis a shorter alias forlets self upgrade.lets self upgrade --preopts an installer-managed copy into prereleases.lets --versionworks without alets.yamlin the current directory.- Root and
selfhelp follow the same predictable help path. - Help output has better spacing and alignment, including long command names and example blocks.
- Zsh completion understands root flags before commands and respects
--. source <(lets completion -s zsh)no longer hangs under shell job control.- Self-upgrades and remote downloads show progress in interactive terminals while staying quiet in redirected output.
- Pressing Ctrl-C now stops a self-upgrade while its release or checksum file is downloading.
Better editor navigation, without a C toolchain
The built-in language server can now follow more of the relationships inside real-world Lets configs:
ref: buildnavigates to the referenced command.- YAML merge aliases such as
<<: *testnavigate to their anchor. - Commands defined in local mixins are indexed for navigation.
- Malformed local mixins report a normal parse error instead of crashing.
At the same time, the YAML parser moved to a pure-Go implementation. Building Lets no longer requires the C toolchain introduced around 0.0.55.
The zsh completion path is more reliable too. Root flags such as -c and --config work before command names, -- is respected as the end of root flags, and loading completion with process substitution no longer hangs:
source <(lets completion -s zsh)
Help AI coding agents use the tasks you already have
Lets now ships an experimental Agent Skill: a portable set of instructions that tells compatible coding agents to inspect lets.yaml, discover commands, respect mixins, and prefer the project's task interface.
Inspect the bundled skill:
lets self skills show
Install it for the current repository:
lets self skills install --local
Or install it for your user:
lets self skills install --global
When a newer Lets release includes improved guidance, update known local and global copies with:
lets self skills update
See Agent Skills for install paths, custom locations, and update behavior.
Before you upgrade from 0.0.55
Most changes are additive, but four migrations are worth checking:
-
The deprecated
eval_envdirective has been removed. Replace it withenvusingshmode:env:GIT_SHA:sh: git rev-parse --short HEAD -
The top-level
--upgradeflag moved tolets self upgrade. -
Unknown commands now exit with status 2. If a script asserts the old status, update it.
-
Old checksum syntax still works, but is deprecated. Run
lets self fix --dry-runto preview the new structure, thenlets self fixto migrate the main config and local mixins.
If you build Lets from source, version 0.0.63 requires Go 1.26.
Version 0.0.56 was not published because of build issues; its configuration improvements shipped in the releases that followed.
The full list
This post focuses on the changes that affect everyday workflows. For every fix, refactor, and dependency update, read the changelog.
You can also compare v0.0.55...v0.0.63.
Thank you to everyone who reported issues, tested release candidates, and contributed changes. If something does not behave as expected, please open an issue.
