Documentation — Lumos CLI Framework for Lua
Everything you need to build, style, and ship production-ready CLI applications with Lumos.
Installation
Lumos requires Lua 5.1+ or LuaJIT, and LuaRocks >= 3.8. The recommended way to install is via LuaRocks:
# Install from LuaRocks
luarocks install --local lumos
# Add to PATH
echo 'export PATH="$HOME/.luarocks/bin:$PATH"' >> ~/.bashrc
source ~/.bashrcVerify the installation by running lumos version.
Quick Start
Create a new CLI application in minutes. Below is a minimal example that defines a command with an argument and a flag:
local lumos = require('lumos')
local app = lumos.new_app({
name = "mycli",
version = "0.3.8",
description = "My first CLI app"
})
local greet = app:command("greet", "Greet someone")
greet:arg("name", "Name to greet")
greet:flag("-u --uppercase", "Use uppercase")
greet:action(function(ctx)
local msg = "Hello, " .. (ctx.args[1] or "World") .. "!"
if ctx.flags.uppercase then
msg = msg:upper()
end
print(msg)
return true
end)
os.exit(app:run(arg))Run it with lua main.lua greet Alice -u to see the uppercase greeting.
Ecosystem
Lumos ships with 38 built-in modules covering everything from argument parsing to documentation generation.
lumos.app App builder with fluent API for commands, args, flags, and actions.
lumos.core Backward-compatible facade delegating to parser, validator, executor, and help renderer.
lumos.parser Parses CLI arguments into structured data with typo suggestions.
lumos.validator Validates positional arguments and flags against definitions and constraints.
lumos.executor Executes commands with validation, hooks, middleware, and error handling.
lumos.help_renderer Generates formatted help text with colors, alignment, and metadata.
lumos.flags Flag validators for int, number, email, url, and path types.
lumos.color ANSI colors with automatic terminal detection and template formatting.
lumos.format ANSI text styles, truncation, word wrap, padding, and case transformations.
lumos.table Boxed tables, data tables with headers, and key-value layouts.
lumos.progress Progress bars with classic, unicode, and block styles plus ETA.
lumos.loader Loading animations and spinners with multiple styles and success/failure transitions.
lumos.prompt Input, password, confirm, select, multiselect, forms, wizards, autocomplete, search, and validators.
lumos.config JSON, YAML, TOML and key=value config loading, env vars, and merged settings.
lumos.json Standalone JSON encoder/decoder without external dependencies.
lumos.yaml Standalone YAML encoder/decoder supporting anchors, aliases, and block scalars.
lumos.security Shell escaping, path sanitization, validation, and in-memory rate limiting.
lumos.logger Structured logging with levels, timestamps, colors, and child loggers.
lumos.completion Bash, Zsh, Fish, and PowerShell completion script generators with enum value and subcommand support.
lumos.toml TOML parser for configuration files.
lumos.env_loader Environment variable loading and prefix-based extraction.
lumos.manpage Man page generation for your entire CLI or single commands.
lumos.markdown Markdown documentation generator with TOC and examples.
lumos.bundle Single-file Lua script bundler with dependency resolution and build cache.
lumos.native_build Native binary compiler with static linking and bytecode support.
lumos.package Standalone executable packager using precompiled launchers for Linux, macOS, and Windows — no C compiler needed.
lumos.plugin Lightweight plugin system to extend apps and commands with reusable extensions.
lumos.middleware Express-like middleware chain for pre/post action hooks, auth, dry-run, retry, and rate-limiting.
lumos.profiler Integrated profiling to measure command execution time and identify bottlenecks.
lumos.error Typed error system with structured exit codes, context, and user-friendly messages.
lumos.error_codes Standardized exit codes: SUCCESS, INVALID_ARGUMENT, MUTEX_VIOLATION, and more.
lumos.version Framework version information.
lumos.config_cache In-memory configuration cache with automatic invalidation via file mtime.
lumos.fs Cross-platform file system utilities: read, write, mkdir, and path checks.
lumos.http Lightweight HTTP client using curl: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS with JSON and auth support.
lumos.platform OS and architecture detection, TTY support, color capability checks.
lumos.terminal Terminal control: dimensions, cursor positioning, and screen clearing.
lumos.runtime_manager Discovers, downloads, and syncs native launcher binaries per platform.
App Builder
The lumos.new_app() function creates an application instance. You can configure the name, version, description, and optional auto-loading for config files and environment variables.
name— your CLI binary nameversion— semantic version stringdescription— short description shown in helpconfig_file— path to auto-loaded config (JSON or key=value)env_prefix— prefix for auto-loaded environment variables
Commands & Arguments
Commands are created with app:command(name, description). Use the fluent API to add arguments, flags, examples, and aliases. command:option(spec, description) is a convenient alias for flag_string.
app:command("greet", "Greet someone")
:arg("name", "Name to greet")
:flag("-u --uppercase", "Use uppercase")
:alias("hi")
:examples({"mycli greet Alice", "mycli greet Bob -u"})
:action(function(ctx)
print(ctx.args[1] or "World")
end)Group commands in help output with command:category("Group Name").
Flags & Validation
Lumos supports boolean, string, integer, email, URL, and path flags. Each flag is validated automatically at runtime. You can also set ranges for integers. In 0.3.8+, chain fluent modifiers like :default(), :env(), :required(), and :validate() directly after any flag definition.
local cmd = app:command("deploy", "Deploy app")
cmd:flag_int("-p --port", "Server port", 1, 65535)
cmd:flag_string("-e --env", "Environment")
cmd:flag_email("--notify", "Notify email")
cmd:flag_url("--endpoint", "API endpoint")
cmd:flag_path("-c --config", "Config file")
cmd:persistent_flag("-v --verbose", "Verbose output")Subcommands
Commands can contain nested subcommands. Use command:subcommand(name, description) to build hierarchies like git remote add.
Exit Codes & Suggestions
Lumos returns standard exit codes (EXIT_OK, EXIT_ERROR, EXIT_USAGE) and prints errors to stderr. If a user mistypes a command, Lumos suggests the closest match with a "Did you mean?" message.
local core = require('lumos.core')
-- app:run() returns EXIT_OK, EXIT_ERROR or EXIT_USAGE
local code = app:run(arg)
os.exit(code)
-- Typing a command that doesn't exist shows a suggestion:
-- Error: Unknown command 'gree'
-- Did you mean: greet?Persistent Flags
Persistent flags are inherited by subcommands. Define them at the app level with app:persistent_flag() or at the command level with command:persistent_flag(). Typed variants are also available: persistent_flag_string, persistent_flag_int, and persistent_flag_email.
Hooks
Attach pre_run and post_run hooks to individual commands, or set global hooks with app:persistent_pre_run() and app:persistent_post_run(). Hooks receive the execution context and are great for setup, teardown, logging, and validation.
local greet = app:command("greet", "Greet someone")
greet:pre_run(function(ctx)
print("Starting greeting...")
end)
greet:post_run(function(ctx)
print("Greeting done.")
end)
-- App-level hooks run for every command
app:persistent_pre_run(function(ctx)
print("Global setup")
end)Middleware
Middleware in Lumos works like Express.js: each middleware function can inspect, modify, or short-circuit a command execution. Global middleware runs for every command, while command-level middleware only applies to specific commands. This is ideal for cross-cutting concerns like authentication, logging, rate limiting, and dry-run simulation.
local lumos = require('lumos')
local mw = lumos.middleware
-- Global middleware runs for every command
app:use(mw.builtin.logger(), 10)
app:use(mw.builtin.auth({env_var = "API_KEY"}), 50)
app:use(mw.builtin.rate_limit({max_requests = 10, window_seconds = 60}), 30)
-- Command-level middleware
local deploy = app:command("deploy", "Deploy app")
deploy:use(mw.builtin.confirm({
message = "Are you sure you want to deploy to production?",
default = false
}), 20)
deploy:use(mw.builtin.retry({max_attempts = 3, backoff = "exponential"}), 40)Built-in middleware includes: logger (structured request logging), auth (API key validation via environment variable), dry_run (preview mode without side effects), confirm (interactive confirmation), rate_limit (in-memory request throttling), retry (exponential backoff on failure), timeout (abort slow commands), circuit_breaker (stop calling failing commands after repeated errors), and verbosity (automatic debug output). Lower priority numbers run earlier in the chain.
Countable Flags
Flags that accumulate repetitions are common for verbosity levels (-v, -vv, -vvv). Mark a flag as countable and Lumos will track how many times it was used.
cmd:flag("-v --verbose", "Increase verbosity"):countable()Running myapp -vvv sets ctx.flags.verbose = 3. Use this with the verbosity middleware to auto-adjust log levels.
Advanced Flags
Flags support fluent modifiers for richer validation and better UX. Chain :default(), :env(), :required(), and :validate() directly after any flag definition.
local deploy = app:command("deploy", "Deploy app")
deploy:flag_string("-e --env", "Environment")
:default("dev")
:env("DEPLOY_ENV")
:required(true)
:validate(function(v)
return v == "dev" or v == "prod"
end)
deploy:flag_int("-p --port", "Server port", 1, 65535)
:default(8080)Positional Arguments
Validate positional arguments with required, type, min/max constraints, default values, and custom validate functions.
local greet = app:command("greet", "Greet someone")
greet:arg("name", "Name to greet", {
required = true,
type = "string",
validate = function(v)
return #v >= 2
end
})
greet:arg("count", "How many times", {
type = "number",
min = 1,
max = 10,
default = 1
})Plugins
Extend apps and commands with reusable plugins via plugin.use(target, plugin, opts) or chain command:plugin(plugin, opts) directly after defining a command.
local lumos = require('lumos')
local plugin = require('lumos.plugin')
-- Define a reusable plugin that adds a --debug flag
local my_plugin = function(target, opts)
target:persistent_flag("--debug", "Enable debug mode")
end
-- Register globally on the app
plugin.use(app, my_plugin, {verbose = true})
-- Or attach directly to a specific command
local deploy = app:command("deploy", "Deploy app")
deploy:plugin(my_plugin)Advanced Patterns
Beyond basic flags and commands, Lumos provides patterns for evolving and securing your CLI. Use mutex_group when two flags cannot be used together (for example, --file vs --url). Mark internal flags as hidden to keep your --help clean. Deprecate old flags gently with migration messages instead of breaking your users.
local deploy = app:command("deploy", "Deploy app")
-- Mutually exclusive flags: only one input source allowed
deploy:flag_string("-f --file", "Config file")
deploy:flag_string("-u --url", "Config URL")
deploy:mutex_group("input", {"file", "url"}, {required = true})
-- Hidden flag: internal use, not shown in --help
deploy:flag("--internal-token", "Service token")
:hidden_flag(true)
-- Deprecated flag: warns user to migrate
deploy:flag("--legacy-deploy", "Old deploy path")
:deprecated("Use --strategy=bluegreen instead")When a deprecated flag is used, Lumos prints a clear warning to stderr suggesting the replacement. Hidden flags remain fully functional but are only visible when LUMOS_DEBUG=1 is set. Mutex groups validate before the action runs, so users get immediate feedback.
Colors & Format
The lumos.color module provides ANSI colors with automatic terminal detection. It respects NO_COLOR and LUMOS_NO_COLOR. The lumos.format module adds text styles like bold, italic, underline, and case transformations.
local color = require('lumos.color')
print(color.green("Success!"))
print(color.red("Error!"))
print(color.bold(color.yellow("Warning")))
-- Template-based
local out = color.format("{green}OK {reset}Done")
print(out)
-- Status and log helpers
print(color.status.success("Done"))
print(color.log.info("Ready"))Format
The lumos.format module provides ANSI text styles (bold, italic, underline), truncation, word wrapping, padding, and case transformations independent of colors.
local format = require('lumos.format')
print(format.bold("Bold"))
print(format.truncate("very long text", 10))
print(format.title_case("hello world"))
print(format.pad_center("hi", 10))
print(format.uppercase("hello"))Tables
Lumos includes three table renderers: boxed for simple lists, create for full data tables with headers, and simple for borderless layouts. Column widths are calculated automatically. For create, you can constrain widths with min_width and max_width options.
local tbl = require('lumos.table')
-- Boxed list
print(tbl.boxed({"A", "B", "C"}, {
header = "Items", align = "center"
}))
-- Data table
print(tbl.create({
{name="Lua", year=1993},
{name="Moon", year=2020}
}, {headers={"name","year"}}))You can also render key-value pairs with tbl.key_value(data, {simple = true}).
Split large tables into pages with tbl.paginate() or retrieve a single page with metadata via tbl.page().
local tbl = require('lumos.table')
-- Split into pages
local pages = tbl.paginate(rows, 10)
-- Get a single page with metadata
local result = tbl.page(rows, 1, 10)
print(result.page, "/", result.total_pages)Progress Bars
Track long-running operations with customizable progress bars. Styles include classic, unicode blocks, and smooth sub-block rendering.
local progress = require('lumos.progress')
-- Simple one-liner
for i = 1, 100 do
progress.simple(i, 100)
end
-- Advanced bar
local bar = progress.new({
total = 100,
width = 40,
style = "unicode"
})
for i = 1, 100 do
bar:increment(1)
os.execute("sleep 0.02")
end
bar:finish()Loader
Display loading animations and spinners. Supports multiple animation styles and clean success / failure transitions.
local loader = require('lumos.loader')
loader.start("Processing...")
for i = 1, 50 do
loader.next()
-- do work
end
loader.success()Prompts
Build interactive CLIs with input, password, confirmation, single-select, and multi-select prompts. Arrow-key selection works on Unix-like terminals; graceful fallbacks are provided for Windows and limited terminals.
local prompt = require('lumos.prompt')
local name = prompt.input("Your name", "Anon")
local secret = prompt.password("Password")
local ok = prompt.confirm("Continue?", true)
local ix, choice = prompt.select("Pick one",
{"dev", "staging", "prod"})
local selections = prompt.multiselect("Features",
{"logs", "metrics", "alerts"})Interactive CLI
Lumos includes higher-level helpers for building rich interactive experiences: number() with min/max constraints, required_input() with validators, autocomplete(), search(), editor() for multi-line input, plus form() and wizard() builders.
local prompt = require('lumos.prompt')
-- Constrained number input
local port = prompt.number("Port", 1024, 65535, 8080)
-- Required input with validator
local email = prompt.required_input(
"Email",
prompt.validators.email,
"Enter a valid email")
-- Autocomplete from options
local lang = prompt.autocomplete(
"Language",
{"Lua", "Python", "Rust"},
"Lua")
-- Searchable selection
local _, tool = prompt.search("Tool",
{"Docker", "Kubernetes", "Terraform"})
-- Multi-step wizard
local settings = prompt.wizard("Setup", {
{
title = "Project",
fields = {
{name="project", label="Name", type="input", required=true},
{name="license", label="License", type="select",
options={"MIT", "Apache-2.0"}, default=1}
}
},
{
title = "Config",
fields = {
{name="port", label="Port", type="number",
min=1024, max=65535, default=8080},
{name="ssl", label="Enable SSL?", type="confirm", default=true}
}
}
})Available validators include email, number, integer, url, non_empty, one_of(options), and regex(pattern). Use prompt.validate() to wire custom validation into any prompt.
Configuration
Load JSON, YAML, TOML, or key=value config files, read environment variables by prefix, and merge multiple sources with predictable priority.
local config = require('lumos.config')
local file = config.load_file("config.json")
local env = config.load_env("MYAPP")
local settings = config.merge_configs(
{timeout = 30},
file,
env,
ctx.flags
)Validate configuration against a schema before using it.
local config = require('lumos.config')
local schema = {
host = {required = true, type = "string"},
port = {type = "number", validate = function(v) return v > 0 end}
}
local ok, errors = config.validate_schema(settings, schema)
local data, err = config.load_validated("config.json", schema)YAML
The built-in lumos.yaml module provides encoding and decoding without external dependencies. It supports scalars, sequences, mappings, block literals, folded blocks, anchors, and aliases.
local yaml = require('lumos.yaml')
local data = yaml.decode("name: Alice
age: 30")
print(data.name) -- Alice
local out = yaml.encode({name = "Bob", age = 25})JSON
The built-in lumos.json module provides robust encoding and decoding without external dependencies. It handles unicode escapes and surrogate pairs correctly.
Logging
Lumos provides a structured 5-level logger with optional timestamps, colored output, context serialization, file output, and child loggers. Configure outputs, levels, and colors programmatically or via environment variables. Switch to JSON output for machine-readable logs.
local logger = require('lumos.logger')
logger.set_level("DEBUG")
logger.info("Server started", {port = 8080})
logger.error("Connection failed", {host = "db"})
local child = logger.child({component = "http"})
child.debug("Request received", {path = "/api"})logger.set_format("json")
logger.info("Server started", {port = 8080})Quiet Mode
Lumos supports a global --quiet / -q flag out of the box. When enabled, only errors are printed. This is useful for scripting and CI pipelines.
$ myapp deploy --quietSecurity
Protect your CLI with input sanitization, safe path validation, shell escaping, email/URL validation, in-memory rate limiting, and elevation detection. Use security.safe_open() and security.safe_mkdir() for guarded filesystem operations.
local security = require('lumos.security')
local safe, err = security.sanitize_path(user_path)
local escaped = security.shell_escape(user_input)
local ok = security.validate_email(email)
local clean = security.sanitize_output(raw_text)
-- Rate limiting
local allowed, msg = security.rate_limit("api", 10, 60)HTTP Client
The lumos.http module provides a lightweight HTTP client backed by curl. It supports GET, POST, PUT, PATCH, DELETE, HEAD, and OPTIONS with automatic JSON encoding, Bearer/Basic auth, custom headers, query parameters, and timeouts — all with zero extra Lua dependencies.
local http = require('lumos.http')
-- GET with query params
local resp, err = http.get("https://api.example.com/users", {
query = {page = "1", limit = "10"}
})
-- POST with JSON body (auto-encoded)
local resp, err = http.post("https://api.example.com/users", {
body = {name = "Alice"},
auth = {bearer = "token"}
})
-- Response helpers
if resp and resp.ok then
local data = resp.json()
endThe response object includes status, body, headers, ok, and a json() helper for lazy decoding. When the body is a table and json is not explicitly disabled, it is automatically encoded to JSON and the Content-Type: application/json header is added.
Testing
Lumos is built with testability in mind. Every command action receives a context object, making it easy to simulate inputs and assert outputs. Generated projects include a .busted configuration and a starter test file. You can run the full suite with make test.
-- tests/app_spec.lua (Busted)
local lumos = require('lumos')
describe("greet command", function()
it("greets by name", function()
local app = lumos.new_app({name = "test"})
local greet = app:command("greet", "Greet")
greet:arg("name", "Name")
greet:action(function(ctx)
print("Hello, " .. (ctx.args[1] or "World"))
return true
end)
-- Run with simulated args
local code = app:run({"greet", "Alice"})
assert.are_equal(lumos.core.EXIT_OK, code)
end)
end)Because app:run(args) accepts any table of strings, you can programmatically test commands without spawning subprocesses. The returned exit code (EXIT_OK, EXIT_ERROR, EXIT_USAGE) lets you assert both happy paths and error conditions in a single test file.
Shell Integration
Generate Bash, Zsh, and Fish completion scripts, man pages, and Markdown documentation directly from your application definition.
-- Built-in completion command
app:add_completion_command()
-- Generate a specific shell
app:generate_completion("bash")
app:generate_completion("zsh")
app:generate_completion("fish")
app:generate_completion("powershell")
-- Generate all shells to a directory
app:generate_completion("all", "./completions")
-- Custom completion values for flags
cmd:flag_enum("--env", "Environment", {"dev", "staging", "prod"})
:complete({"dev", "staging", "prod"})
-- Man pages
app:generate_manpage(nil, "./man")
-- Markdown docs
app:generate_docs("markdown", "./docs")Bundling & Distribution
Lumos provides three ways to distribute your CLI. lumos bundle creates a portable Lua script. lumos package creates a standalone executable using precompiled launchers for Linux, macOS, and Windows — no C compiler required. lumos build compiles a native binary with an embedded Lua VM and requires a C toolchain (supports static linking and user C modules when their .a archives are available).
Both bundle and build use an automatic cache in .lumos/cache/ to speed up repeated builds. The bundler now resolves modules cleanly via package.searchers instead of monkey-patching require.
Use lumos doctor to check your local environment (Lua, LuaRocks, LuaFileSystem, Lumos) for common issues.
# Bundle into a portable Lua script
lumos bundle src/main.lua -o dist/myapp
# Package into a standalone binary (Linux, macOS, Windows — no C compiler)
lumos package src/main.lua -o dist/myapp
# Build a native binary (requires C toolchain)
lumos build src/main.lua -o dist/myapp --static
# Analyze dependencies
lumos bundle src/main.lua --analyzeDistribution Guide
Choosing the right distribution method depends on your users' environment and your performance requirements. Use bundle when transparency matters and Lua is already installed. Use package for a zero-dependency executable without installing a C compiler. Use build when you need maximum performance, static linking, or embedding of C modules like lfs or lpeg.
-- When to choose which distribution method
-- 1. BUNDLE: target has Lua installed, you want transparency
$ lumos bundle src/main.lua -o dist/myapp
-- 2. PACKAGE: zero-dependency executable, no C compiler needed
$ lumos package src/main.lua -o dist/myapp
-- 3. BUILD: maximum performance, static linking, native binary
$ lumos build src/main.lua -o dist/myapp --static
-- Cross-compile from Linux to Windows
$ lumos package src/main.lua -o dist/myapp -t windows-x86_64
$ lumos build src/main.lua -o dist/myapp -t windows-x86_64
-- Check your environment before building
$ lumos doctorThe lumos doctor command checks your local environment for Lua, LuaRocks, LuaFileSystem, and available build toolchains before you distribute. Cross-compilation is supported for Windows from Linux hosts, and macOS targets are handled via precompiled launchers when building from other platforms.