Lenso CLI
An embeddable Rust CLI framework and a standalone lenso executable. Commands come
from native handlers or explicitly declared extension manifests. Project changes
are an optional extension; running a command does not require a file plan.
Version 1.0 migrates the crates.io names lenso-cli and lenso from the previous
main-repository implementation to this standalone CLI project. lenso-cli is now
the embeddable CLI engine; lenso installs the executable. This is a breaking
product/API migration, not a compatible SDK upgrade. Existing main-framework
consumers should retain their 0.x constraints. See the 1.0 migration.
Source: LioRael/lenso-cli. The previous CLI implementation is preserved in LioRael/lenso-cli-previous.
Native binaries and SHA-256-verified installers are distributed through GitHub Releases for macOS (Intel/Apple Silicon), Linux (x64/ARM64) and Windows x64. See installation and release instructions, including private-repository downloads. Neither installation nor the native CLI requires Node.js, Git or Rust.
The execution framework owns workflows, questions, hooks, and JSON/Agent output.
The runtime owns restricted Host operations, extension distribution, integrity,
locking, and the user cache. Business protocols live under extensions/; the
runtime does not parse component catalogs, npm metadata, or workspace formats.
Start a project
The 0.2 CLI provides native onboarding; editing TOML is optional:
# Existing projects
new creates a Vite/React/TypeScript/Tailwind project through the shadcn preset.
init adopts an existing prepared project. The Code extension asks for its GitHub
repository; --input '{"repo":"owner/repo","ref":"v1","catalog":"catalog.toml","target":"src"}'
provides the same settings to Agents. Configurations and proposed grants are reviewed
before saving; application changes have a separate file preview and confirmation.
Frontend development and approved package-manager operations still require the
project's own toolchain. Fetching and running Lenso extensions requires no Node.js or Git.
Official aliases resolve through lenso-registry.
lenso ext add @acme/tool --constraint '^1' uses an explicitly configured namespace;
lenso ext add github-release:owner/repo/tool --id tool uses a direct source.
lenso preset apply NAME_OR_URL composes packages and runs its adoption recipe.
Private packages and authenticated API quota use explicit --token-env NAME;
--api URL selects a compatible GitHub API. Credentials remain in the environment,
and only their binding names are saved.
lenso ext remove ID and lenso preset remove ID retain packages still selected by
another preset or required by another extension. They leave generated code and the
shared cache in place. A new project without extensions uses --preset empty.
These commands require CLI 0.2 or a current source build; the earlier 0.1 binary continues to provide its original command set.
Composable code modules
The next Code extension source version (0.2.0, not yet published) adds
code compose-preview and code compose for capability-based module composition.
Module instances select compatible adapters independently, share resources only
through explicit bindings, track file ownership, reconcile incremental updates and
safely clean up obsolete generated files through one reviewed plan. The generic runtime
retains its existing Host and authorization responsibilities.
See module composition and the coordinated SDK/Registry design for the working catalog format, author setup, Agent input and implementation limits.
The author workflow also supports strict templates, isolated generated directories,
code validate and complete named samples. See authoring code generators
and the compilable Rust two-adapter example.
These author features require a source build of native CLI 1.0.0.
Code's optional native dependency management applies Cargo/npm declarations to real manifests, preserves shared ownership, and proposes separately authorized installer commands with failure/retry state.
Configuration and dependencies
The editable project configuration lives in root lenso.toml. Commands prepare
their dependencies automatically; no install command is required. Commit the
root declaration and generated lenso.lock. Keep local source packages available
when using local declarations.
[]
= 1
[[]]
= "demo"
= "lenso/extensions/greeting/extension.toml"
= ["greeting.txt"]
Sources can be local paths, explicit HTTPS manifests, or package references:
[]
= 1
= { = "https://packages.example.com/index/" }
[[]]
= "code"
= "registry:@acme/code"
= "^1.2"
A Registry reference selects the highest compatible non-yanked release from the
configured static index. Manifest identity/version and content digests are checked
before registration. Direct-source version requirements remain validation checks.
Remote manifests must pin exact Wasm digests; local sources may use auto.
See extension distribution for the protocol, lock semantics,
limits, and command-level acceptance. The official @lenso namespace is built in.
GitHub Releases and repository code sources are also supported. See
GitHub distribution for author configuration,
packaging, source grants, lock/cache behavior, and the optional code extension.
The design record distinguishes later goals such as
repository-hosted executable packages and installed-file receipts.
Extensions declare dependencies with provider IDs, sources, and semantic version
constraints. Resolution supports transitive dependencies, shared providers, version
backtracking, cycles/conflicts, and a complete locked graph. Each provider has one
version per project. Dependencies receive no inherited grants. The optional native
project provider is activated only when selected or required; a minimal executable
can omit it with cargo build -p lenso --no-default-features.
When upgrading an existing 0.1 project, run lenso update to obtain packages with
explicit dependencies. For an intentionally pinned older package, select its native
provider explicitly with lenso ext add project.
The first command resolves metadata, validates definitions, and saves the lock.
Each Wasm file is downloaded or copied into the user cache only when a handler
needs it. Subsequent commands use locked metadata and verified cached bytes.
Changed declarations are resolved automatically, preserving unchanged pins;
lenso update explicitly refreshes sources. Source-file edits become active after
that update. Permissions stay in project TOML and are never shared through cache.
--locked requires an existing lock matching the declarations. --offline disables
network access during dependency preparation; local sources and cached metadata/
modules remain usable. This flag does not sandbox explicitly approved child processes
or replace a workflow's own network policy. --json keeps the same result protocol
during both cold and warm execution.
Help uses locked metadata without touching the cache. Without a lock, help resolves
only metadata and writes no project lock; local auto digests require reading the
local source bytes. Remote Wasm is not downloaded by help. --version does not load
project dependencies.
GitHub packages and code resources
# Author: inspect, then publish an already built extension.
# Consumer: add the optional Code extension and answer its repository question.
Release extensions use source = "github-release:owner/repo[/package]" with a
semantic version requirement. Code catalogs use repository snapshots selected by
an extension's opaque options and separate sources = ["github:owner/repo"] grants.
Node.js and Git are not needed for either consumer path. Repository source pins live
beside extension pins in lenso.lock; immutable bytes reuse the user cache. Read-only
commands may prepare these metadata pins, while file application still requires the
existing project grants and review. Official packages use curated Registry entries; custom repositories remain supported.
Publishing validates deterministic packages, asks for approval, uploads to a draft,
verifies checksums and then publishes. Reruns resume matching draft assets and accept
identical published releases without mutation. --draft keeps a verified draft;
--json, --answers and --yes use the existing Agent protocol. See the
author guide for token
configuration, discovery prerequisites and the included GitHub Actions workflow.
See live verification for the real Release
uploads, successful Actions run and Linux/macOS consumer results.
Shared cache
Downloaded metadata and Wasm use content-addressed blobs under the per-user cache:
- macOS:
~/Library/Caches/lenso - Linux:
$XDG_CACHE_HOME/lenso, or~/.cache/lenso - Windows:
%LOCALAPPDATA%/Lenso/Cache
lenso cache path prints the active location. Set an absolute LENSO_CACHE_DIR to
override it, including in CI. The v1/blobs/sha256/ store is shared across projects;
per-entry OS locks and atomic replacement coordinate simultaneous downloads. Cached
bytes are checked against project pins. Missing/corrupt blobs are restored from the
locked source; deleting cache never changes dependency versions. Cache does not
contain project permissions, answers, reviewed file plans, or node_modules.
Wasm compilation is still performed by Extism at invocation time.
The old project .lenso/cache/ is no longer used. lenso migrate --project PATH
moves the experimental .lenso/project.json layout into visible sources and root
TOML/lock, preserving permissions and unrelated local files. An existing root
configuration requires a manual merge. Without --project, the nearest ancestor
containing lenso.toml is selected.
Terminal and Agent output
Interactive terminals show progress, completed steps, changed paths, external
commands, and a compact result summary. inquire handles text, searchable select,
multiselect, and confirmation prompts; indicatif handles progress; console
styles results and diffs. Clap handles command parsing and help.
--color auto|always|nevercontrols colors. Auto respectsNO_COLORand nonterminal output; explicitalwaysoverridesNO_COLORin human mode.--debugincludes the complete result report on stderr in human mode.--no-interactive, CI, and nonterminal input disable questions and progress animations. Missing answers exit 2; they are never chosen implicitly.--jsonalways disables prompts, progress, and host color codes. The existing result/diagnostic schema stays intact; child process logs stay on stderr.
Other engine examples
Without a declared preset, lenso --help exposes only generic engine commands.
The native process example and branded Acme CLI do not need a project file plan.
Embed it
The framework package is lenso-cli; the standalone binary package is lenso.
No crate has been published. Use a local path dependency while developing.
use ;
async
See the Acme example for a native question and project extension discovery. Native Rust handlers are trusted application code; they are not sandboxed.
What is implemented
- Dynamic command paths, string/boolean/list arguments, help with providers, namespaced
run, and explicit alias conflict handling. Installations can disable or rename aliases. - Async native handlers, validated step dependency graphs, prepare/before/validate/ after hooks, resumable questions, and dependent-state invalidation.
- Text, select, multiselect, and confirmation questions shared by terminal and JSON modes.
- Local Extism modules pinned by SHA-256, JSON ABI v1, fuel/time/memory/input/output limits, no WASI, and explicit host APIs for scoped file reads, Registry credentials, workspace discovery, and HTTP URL prefixes.
- Exact process grants, shell-free invocation, stderr logs, nonzero exit propagation, and Ctrl-C supervision. Unix cancellation kills the process group.
- Exact file destination grants checked before reading preimages or applying changes; calling the optional project extension does not bypass the installation's grants.
- Optional file replacement plans, unified diffs, content-bound approval, preimage checks, read-input guards, source/module provenance, staging, and atomic replacement per file. Reviewed dependency commands require their own grant and confirmation.
The shadcn extension consumes official names, custom Registry URLs, namespaces, and local JSON resources in prepared TypeScript/Tailwind 4 projects and monorepos. The extension runs as Wasm; Rust owns reads, credentials, HTTP, review, application, and dependency processes. The Vite fixture remains the runnable button demo.
Develop the shadcn extension
The local development workflow requires Rust 1.99+, the wasm32-unknown-unknown target, Python 3, Node.js, and npm.
The configuration script builds a local development Wasm artifact and writes the
declaration; it does not prepare the lock or cache. The included fixture pins
React 19, Vite 8, TypeScript 7, and Tailwind 4.
# Static discovery: commands come from the declared preset.
# Live Registry resolution and a complete diff, without changing application files.
# Choose from the live catalog, review files, then authorize package installation.
# Agent/CI uses the same workflow and explicit consent.
# Build and open the installed React component.
--allow-install records one exact npm process grant; the CLI still requests
consent before writing or executing it. Omit that configuration flag for preview-only
use. --yes answers confirmation questions only; it does not grant permissions or
choose a component. An answers file such as {"item":"button"} works with
--answers FILE. Missing answers in JSON/nonterminal mode return needs_input
and exit 2. Terminal questions use arrow keys and type-to-filter selection. Esc revisits the
previous question; Ctrl-C cancels. File review offers a full diff before application;
external commands require a separate confirmation.
The demo is an existing npm project with @/* -> src/*, Vite aliases, and a
shadcn-compatible Tailwind 4 theme. The preset also routes configured workspace
aliases and package exports. lenso new --preset shadcn supplies the Vite starter;
shadcn init reviews components.json in an existing prepared project.
Multiple resources, custom registries, and workspaces use the same review flow:
See Registry, authentication, monorepo scope and verification.
Boundaries and next work
Extensions are declared as local sources, remote manifests, or Registry package references and prepared
automatically. The official Registry supplies curated names and preset selection.
Native providers come from the embedding host. Discovery reads
static locked metadata; execution checks shared cached bytes and SHA-256. Root
configuration declares file destinations, readable paths, HTTPS prefixes, and exact
process grants. Shadcn commands use the shadcn namespace; core init configures
Lenso, and core update refreshes extension dependencies.
The initial scheduler is sequential. It validates a static graph of at most 256 steps; dynamically added steps and persistent sessions are not implemented. Question-yielding handlers and hooks must be safe to repeat until their step completes. Irreversible work belongs after all interaction and validation.
File plans support UTF-8 create/replace operations, up to 128 files and 1 MiB of content. They bind destinations, recorded read digests, source/module provenance, and external command arguments into an approval digest. A changed destination or read input rejects application. This is optimistic concurrency protection, not a sandbox against hostile filesystem races. Replacements are individually atomic; commands are not a global transaction. Failed dependency installation leaves the already applied files and reports completed work and recovery context.
Process grants authorize exact executable strings and arguments, not an executable signature or sandbox. Children receive PATH only and can still read tool/project configuration. Unix process-group cancellation is covered; Windows termination has not been tested on a Windows host. Secret-scoped answers, shell completion, dynamic step graphs, and persistent sessions remain future work.
The shadcn preset's supported payloads and remaining limits are described in
its compatibility scope. The generic engine has no dependency on
package.json, shadcn, Lenso Application, or Console.
Validate
Design basis: CLI architecture and Lenso product boundaries.