LILYLILYDOCS
Docs/Concepts/Plugins

Plugins

Every non-trivial subcommand of lly is a plugin you can install, replace, or write.

The lly binary is small on purpose. It handles argument parsing, configuration, credential storage, plugin resolution, and a thin dispatch layer. Everything language-specific or fleet-specific lives in a plugin. A C frontend is a plugin. The Next.js builder is a plugin. The deploy command is a plugin. The runtime that executes a brick is a plugin. This keeps the core stable and lets each capability ship on its own cadence.

Where plugins live

Plugins install to ~/.lly/plugins/<name>/. Each directory contains a single executable named after the plugin and a manifest.toml with version, signature, supported file extensions, and dependency notes. When you invoke lly nextjs build, the core resolves nextjs against the plugins directory and execs ~/.lly/plugins/nextjs/nextjs build with the remaining arguments and a handful of LLY_* environment variables.

Subcommands you have not heard of are not errors. They are lookups. If ~/.lly/plugins/<name>/ exists, the command runs. If not, lly prints a hint pointing at lly plugin install.

Installing

lly plugin install fetches a manifest from rc.lilylabs.io, picks the artifact for your platform (currently x86_64-linux and aarch64-macos), verifies the SHA-256 against the manifest, checks the detached signature against the LILY release key, and lays the binary down in ~/.lly/plugins/<name>/. Nothing runs as root. Nothing touches /usr/local. Removal is a directory delete.

install
$ lly plugin install nextjs → fetching manifest from rc.lilylabs.io/plugins/nextjs resolved nextjs@0.14.2 · x86_64-linux · 8.4 MB → verifying sha256 a3f1…c7e2 → verifying signature against lily-release-2026 ✓ installed nextjs@0.14.2 to ~/.lly/plugins/nextjs/ registers extensions: .tsx .jsx .mdx · subcommands: routes build dev render export void

Resolving by file extension

lly compile dispatches on extension. A .c file reaches frontend-c. A .cpp file reaches frontend-cpp. A .js or .ts file reaches frontend-js. The mapping is declared in each plugin's manifest, not hard-coded in the core. lly plugin which <ext> reports the winner when more than one plugin claims an extension.

which
$ lly plugin which .tsx nextjs ~/.lly/plugins/nextjs/nextjs also claims: frontend-js (lower priority)

Lifecycle commands

The full surface lives under lly plugin. The day-to-day calls are list to enumerate, info <name> for the manifest, enable and disable to toggle resolution without removing the binary, verify <name> to re-check the signature on disk, and path to print the resolution root. lly doctor walks every installed plugin and reports any with a missing binary, a stale signature, or a manifest that disagrees with the binary.

The pipeline

Plugin pipeline
AUTHORpushrc CIpublishSTORAGEGCSrc-pullverifyUSERlly install~/.lly/pluginsrun
From a push on the internal GitLab to a working subcommand on your machine.

Plugin authors push to lily1/store/<plugin> on the internal GitLab. CI cross-compiles for the supported targets, signs the artifacts, and uploads them to the bucket behind rc.lilylabs.io. The RC machine runs a sixty-second puller that ingests new versions into the registry. From that moment, lly plugin install sees the new version. Existing installs do not change. Users opt in with lly plugin install <name>@<version> or lly plugin update <name>.

Why this shape

Plugins keep the core honest. The C frontend can ship a fix without a new lly release. The runtime can land a Wasmtime bump without dragging the CLI along. A team running an internal mirror of rc.lilylabs.io can serve their own plugin builds without forking the CLI. And because every plugin is a real binary on disk with a real manifest, lly doctor and lly plugin verify can prove the chain end-to-end. The CLI you typed is the binary that was signed.