v0.3.8 Lumos is actively developed. If you encounter a bug or have a feature request, please open an issue

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:

terminal
# Install from LuaRocks
luarocks install --local lumos

# Add to PATH
echo 'export PATH="$HOME/.luarocks/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc

Verify 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:

main.lua
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 name
  • version — semantic version string
  • description — short description shown in help
  • config_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.

commands.lua
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.

flags.lua
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.

exit_codes.lua
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.

hooks.lua
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.

middleware.lua
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.

countable.lua
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.

advanced_flags.lua
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.

positional_args.lua
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.

plugins.lua
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.

advanced.lua
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.

colors.lua
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.

format.lua
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.

tables.lua
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().

tables_paging.lua
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.

progress.lua
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.

loader.lua
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.

prompts.lua
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.

interactive.lua
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.

config.lua
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.

config_validate.lua
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.

yaml.lua
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.

logging.lua
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"})
logging_json.lua
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.

terminal
$ myapp deploy --quiet

Security

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.

security.lua
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.

http.lua
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()
end

The 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.

app_spec.lua
-- 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.

shell.lua
-- 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.

terminal
# 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 --analyze

Distribution 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.

terminal
-- 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 doctor

The 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.

Lumos is actively developed. If you spot a bug, have a question, or want a feature, open an issue on GitHub.