Skip to content

lumenc command reference

lumenc is the Lumen command line tool: it runs apps, checks them, compiles them ahead of time, scaffolds new ones, and drives a running app for automation.

lumenc <command> [arguments]
lumenc --help
lumenc --version

Exit codes

Code Meaning
0 Success.
1 The command ran and failed (parse error, I/O error, lint findings, no match).
2 Usage error: unknown command, missing argument, bad flag value.

lumenc --help, -h, and help print usage and exit 0. lumenc --version and -V print lumenc <version> and exit 0. An unknown command prints usage on stderr and exits 2.

Every subcommand answers --help and -h with its own usage block and exits 0, for example lumenc run --help. A bare help is not a subcommand flag, so lumenc new help still scaffolds a directory named help.

run

lumenc run <dir> [--profile chrome|tracy|stderr]
                 [--headless [--size WxH] [--dpr N] [--ticks N]]
                 [--artifact <file>] [--assets <file.lpak>] [--no-hooks]
                 [--offline]

Runs the app in <dir>. The directory must contain src/main.lmn unless --artifact is given; src/main.css is optional.

Flag Value Default Effect
--profile chrome, tracy, stderr [profile] mode, else off Installs the tracing profiler. chrome writes lumen-trace.json in the current directory; tracy connects to a running tracy-profiler; stderr prints per-system spans live.
--headless - off Runs the whole pipeline (layout, GPU render, scripting, MCP, screenshots) with no window. Subsystems that only make sense for a person at a window stay idle; the audio module, for one, opens no output device.
--size WxH [window] size, else 960x720 Logical viewport size. Requires --headless. Zero dimensions are rejected.
--dpr positive number 1.0 Scales the offscreen render target; screenshot pixels are logical size times dpr. Requires --headless.
--ticks integer unbounded Runs exactly N ticks, then exits through the graceful-close path. Requires --headless.
--artifact path none Loads a precompiled .lmna artifact instead of parsing source. Disables hot reload.
--assets path to a .lpak none Reads images, icons, and sounds from a lumenc bundle archive, keyed by the path relative to <dir>. A path the archive does not carry falls back to disk; fonts always come from disk. An unreadable archive exits 1.
--no-hooks - off Skips the prebuild and prerun hooks.
--offline - off Resolves the app's registry packages from what is already downloaded and never reaches the network. A package that has not been downloaded exits 1.

Both --flag value and --flag=value are accepted for --profile, --size, --dpr, --ticks, --artifact, and --assets.

Without --headless, passing --size, --dpr, or --ticks is a usage error.

run executes the app's [[hooks]] entries: every prebuild hook, then every prerun hook. See lumen.toml.

run resolves the app's registry packages before the app builds, and a requirement that does not resolve fails the run. A module that resolves and then fails to load is a startup banner, and the app runs without it. An installed lumenc on Linux or macOS loads modules directly; one built from source without the dynamic-engine cargo feature skips engine-locked modules with a single stderr line (its capabilities are compiled in) and still loads portable plugins.

If the directory is a Rust, C++, or Python SDK app (detected from its contents, or declared with [app] kind), run hands off to cargo, cmake, or the Python interpreter. Combining a handoff with --headless, --artifact, --assets, --size, --dpr, or --ticks is a usage error.

--profile needs a lumenc built with the profiling cargo feature, and --profile tracy additionally needs profiling-tracy. A default build reports this and exits 1.

Under --headless, SIGINT and SIGTERM (Ctrl+C, Ctrl+Break, or console close on Windows) exit 0 through the graceful-close path.

check

lumenc check <dir> [--offline]

Parses and validates the app without opening a window and without running hooks. Declared compiler plugins do run, in check-only mode: the tree being validated is the tree a build produces, and emit outputs are discarded. The app's registry packages resolve first, which may write lumen.lock; a requirement that does not resolve exits 1. --offline resolves from what is already downloaded. Prints <dir>: ok (N elements, script: yes|none) and exits 0, or prints the parse error and exits 1. A missing <dir> exits 2.

The check covers the markup, the stylesheet, and every script, including the lmn! markup blocks a candela script writes: a block with no single root, a component element the script declares no function for, or a prop naming a parameter that function does not have all fail here. So does markup naming a component it cannot instantiate: a name no candela function declares, a name a <template> already claims, or a component that reaches itself.

In a candela script the check reaches every function body main reaches, plus the body of every function in the app's own script file that annotates all of its parameters. A handler is the second kind, so a body error in one fails the check instead of waiting for the event that runs it. See handler annotations for what a bare parameter costs.

Every command that compiles markup from source (check, run, build, package) prints the parse-time findings to stderr as <severity> <file>:<line>:<col> [<kind>] <message>, with a hint: line when a fix is machine-applicable. Kinds: unknown-attribute, boolean-attribute, bare-interpolation. They are advisory and never change the exit code; run lumenc lint --signals for the stream a build can gate on.

Those commands also warn on stderr when a [pages] include entry names a file that is not there: the page is skipped and the exit code is unchanged.

build

lumenc build <app_dir> <out.lmna> [--no-hooks] [--offline]

Compiles the app ahead of time into a .lmna artifact: parses the entry .lmn file and src/main.css once, runs the cascade, resolves asset and include paths, bakes the script source, and records which engine runs each part of it. A candela script is also compiled to bytecode and stored beside its source. Prints the element count, the output path, and the artifact size.

The artifact carries every fragment the app declares, both the <template> blocks in its markup and the lmn! blocks in its candela scripts, so the compiled app instantiates them with no parser present.

Runs the app's prebuild hooks first unless --no-hooks is given. Declared compiler plugins run as part of the compile; their emit outputs land under .lumen/generated/<plugin>/ in the app directory.

A multi-page app compiles whole: every page goes into the artifact behind the gate that mounts it, together with the page set navigation resolves against.

An <app_dir> that is not a directory, a missing output path, or an extra positional argument exits 2. For an SDK app the output path is ignored and the native build tool runs instead.

Run the result with lumenc run <dir> --artifact <out.lmna>. See Packaging.

add

lumenc add <name>[@<req>] [<dir>] [--plugin] [--config <k>=<v>]...

Declares a registry package in <dir>/lumen.toml and resolves it, so the app is ready to run. <dir> defaults to the current directory. Comments, spacing, and key order in the file are preserved.

Flag Value Default Effect
--plugin - off Declares a compiler plugin under [[plugins]] instead of a runtime dependency under [dependencies].
--config <key>=<value> none A key in the package's own config table. The value is read as TOML, so 7 and true keep their types and anything else is a string. Repeatable.

With no <req>, the newest version the registry publishes is resolved and written as the requirement. Adding a name the app already declares re-pins it rather than declaring it twice; declaring one name in both tables exits 2.

lumenc add shape-tools
lumenc add [email protected] --plugin --config flavor=gfm

remove

lumenc remove <name> [<dir>]

Deletes the package's [dependencies] entry or its [[plugins]] entry and re-resolves what is left. A name neither table declares exits 1.

fetch

lumenc fetch [<dir>] [--locked] [--target <target>] [--offline]

Resolves every version source the app declares and downloads what they resolve to, writing lumen.lock. Every compile path does this on its own; run it alone when the download should be its own step, as in a CI job that caches it.

Flag Value Default Effect
--locked - off Fails rather than changing lumen.lock. A lock that would move exits 1 and names lumenc update.
--target a platform name this machine's Resolves for another platform, which is what lumenc package --target needs. An unknown name exits 2.
--offline - off Uses what is already downloaded and never reaches the network.

An app that declares no registry package says so and exits 0.

update

lumenc update [<name>...] [--dir <dir>]

Re-resolves the app's registry packages against what the registry publishes now and rewrites lumen.lock. With no names every declared package moves as far as its requirement allows; with names, only those do. A name the app does not declare exits 2.

web

lumenc web <app_dir> [--out <dir>] [--base <path>] [--locale <tag>]...
                     [--render static|csr|ssr] [--prerender seeds|run|none]
                     [--runtime|--no-runtime]
                     [--no-hooks] [--lib-dir <dir>] [--strict] [--offline]
                     [--serve] [--port <n>] [--host <addr>]
                     [--allow-host <name>]...

Emits the app as a site. Compiles it exactly as build does, then writes the stylesheet, every file the markup points at, and one HTML document per page with the markup already in it. Prints how many pages it wrote and where.

--render says where a document comes from:

  • static writes the pages, the stylesheet and the assets. No compiled app, no manifest, no runtime, and no boot script in the documents.
  • csr, the default, writes those and the compiled app, the compiled candela program where there is one, the manifest the browser runtime reads, and the runtime itself. The documents load them, and the runtime adopts the markup each page arrived with.
  • ssr writes what a render needs and no documents: a page is produced when it is asked for, by running the app for that request. --serve renders them here; without it the directory is for a server you build on lumen-ssr.

Every mode writes the whole markup tree, so a reader and a crawler get the same document whichever one is set.

--runtime and --no-runtime say whether the documents carry the browser runtime, which is a separate question from where they come from. static already means --no-runtime and csr already means --runtime, so contradicting either is refused and the message names the mode that means it. ssr is the one that leaves it open: --render ssr --no-runtime produces a page for the request that asks and puts no wasm and no boot script in it, so the visitor reads it and nothing takes it over. The compiled app is still written, because that is what the server renders from.

Runs the app's prebuild hooks first unless --no-hooks is given.

The entry page is written as index.html whatever it is keyed as, and a site whose documents a build writes also gets a 404.html holding the app with no page selected, which is what a static host serves for a path that has no document of its own.

Flag Effect
--out <dir> Where the site is written. Default: [web] out_dir, else <app_dir>/dist/web.
--base <path> URL prefix the site is served under. Default: [web] base_path, else /.
--locale <tag> Emit the site in this locale; repeat for more. The first is served from the site root and the rest from /<tag>/. Under --render static and --render csr each is a document tree; under --render ssr each is a tree a render answers in. Default: [web] locales.
--render static\|csr\|ssr Where a page's document comes from: static and csr write it at build time, and ssr produces it for the request that asks. Default: [web] render.
--runtime / --no-runtime Whether the documents carry the browser runtime. Refused against a --render mode that already says the opposite. Default: [web] runtime, else what --render implies.
--prerender seeds\|run\|none Where the state the pages are rendered with comes from: seeds uses [web.seed] and the defaults the markup declares, run starts from those and then runs the app here, writing each page with the state it settles into, none renders the markup alone. run with --render ssr is refused, because a rendered page settles its own state per request. Default: [web] prerender.
--no-hooks Skip the app's prebuild hooks.
--lib-dir <dir> Directory holding lumen-web.wasm and lumen-web.js, instead of the published runtime.
--strict Exit non-zero if the build printed any warning.
--serve Serve the emitted site on 127.0.0.1 and print the address. Ctrl-C stops it. Under --render ssr every page comes from a render.
--port <n> Port to serve on. Default 8787; 0 takes any free port and prints which.
--host <addr> Address to listen on. Default 127.0.0.1. Any other address makes the site reachable from other machines, and the command says so.
--allow-host <name> Let a render ask this host for data; repeat for more. A render reaches nothing that is not named. Applies to --render ssr --serve; a build that renders nothing says so and ignores it.

Serving

--serve is for development and for a site you host yourself: one directory, one machine, one process. Put a reverse proxy in front of it before anyone else uses it, and build your production server around lumen-ssr, which is the renderer --render ssr installs.

Under --render static and --render csr it hands out the documents the build wrote, the way a plain file server does. Under --render ssr the pages come from a render and everything else comes from the directory, so the stylesheet, the compiled app, the assets and the browser runtime are served straight from disk while a page is being rendered. Every address a link in the site produces reaches the page it names: /settings.html is the settings page, and a path with no file behind it reaches the render too, so /user/42 is answered by the user page with /42 on route.segment.

A process renders one request at a time, and requests for pages queue. Serving more at once means more processes behind a proxy; the reason is in Rendering on a server. A site emitted in several locales is rendered in whichever of them the request asks for, by a /<tag>/ prefix on the path or by Accept-Language.

The browser runtime is looked up in this order: --lib-dir, the directory holding the running lumenc, $LUMEN_LIB_DIR, then the download cache. When none of them has both files, lumen-web.tar.gz is downloaded from the release this toolchain uses, checked against the sha256sums.txt published with it, and unpacked into the cache. It is one pair of files for every platform, so the cache holds it under the release alone. A --lib-dir missing either file is answered rather than fetched, since it already said which copy to use.

A warning does not stop the build. The build warns when an asset lives outside the app directory, when a link names no page, when a script is in a language the browser cannot run, when the skin would otherwise be chosen by whichever machine built the site, and when the browser runtime cannot be found. It warns when --allow-host names a host and nothing here renders a page, and when [web] host names a rewrite file a rendered site has no use for. Under --prerender run it also warns when an app is still changing when its budget runs out, when it asks for an address the build will not fetch, when the same page settles differently on a second run, and when the app changed the shape of the tree while it ran, which leaves what it wrote onto its nodes out of the document.

Everything the site says about itself - its address, description, social image, host, locales - is lumen.toml's [web] section. See the web guide and [web].

A missing <app_dir>, an unknown flag, or a mode neither --render nor --prerender has exits 2. --render ssr together with --prerender run fails the build: a page is written with the state a run settled into here, or with the state the app settles into for the request, and not both. So does a runtime setting that contradicts the --render mode. Only a markup app can be emitted as a site, and not one whose [dependencies] name a native library, which a browser cannot load. A candela package is script source and compiles into the app, so an app that depends on one emits as a site like any other.

package

lumenc package <app_dir> [<out_dir>] [--name <name>] [--target <target>]
                         [--lib-dir <dir>] [--static] [--zip] [--no-hooks]
                         [--offline]

Assembles a folder that runs on a machine with no Lumen installation: the app executable, the Lumen runtime library, lumen.toml, and every other file from <app_dir> at the same relative path. Dotfiles, the output directory, and the app's build inputs and build tree are skipped; compiler-plugin outputs under .lumen/generated are the one dot-prefixed tree that ships. Prints one line naming the executable it wrote and how many app files travelled with it.

For a markup app the executable is the launcher with the compiled app inside it, and the markup, stylesheet, and scripts are compiled in rather than copied, so src/ does not travel. A multi-page app packages whole, routing included.

For an SDK app the app's own toolchain builds it first, exactly as lumenc build would, and the folder is assembled around what that produced:

Kind Executable Detected from
Rust the binary cargo build --release reports Cargo.toml depending on lumenui
C++ the executable in the CMake build tree CMakeLists.txt
Python the one pyinstaller --onefile writes a .py importing lumen

[app] kind in lumen.toml overrides detection. An SDK app's markup, stylesheet, and scripts are read at run time rather than compiled in, so those files travel with it. A C++ build that produced several executables packages the most recent and names the rest.

Flag Value Default Effect
<out_dir> path <app_dir>/dist/<name> Where the folder is written. Created if missing; existing files are overwritten.
--name string the app directory's name Names the executable, and the default output directory.
--target see below this machine's platform Packages for another platform.
--lib-dir path none Directory holding the launcher stub and the runtime library to use, instead of looking them up.
--static - off Links one executable holding the engine and the app's declared modules, instead of assembling a folder of shared libraries.
--zip - off Also writes <out_dir>.zip, holding the folder itself.
--no-hooks - off Skips the prebuild hooks.

Targets are linux-x86_64, linux-aarch64, macos-x86_64, macos-aarch64, and windows-x86_64. An unrecognised name exits 2 and lists the ones that exist. Any host can package a markup app for any of them, because that is file assembly rather than compilation. For an SDK app the target triple goes to that app's own toolchain: a Rust app needs the target installed (rustup target add), a C++ app needs CMAKE_TOOLCHAIN_FILE pointing at a toolchain file for that platform and exits 1 without one, and a Python app is frozen against the interpreter doing the freezing and exits 2 for any target but this machine's.

A Rust app links the engine rather than opening it, so what travels beside it comes out of its own build: the engine library cargo produced and the shared Rust standard library both were compiled against. On Windows no linkable engine exists, so the runtime is inside the executable and nothing travels.

On Linux and macOS every other kind carries the shared runtime too: the runtime library there links liblumen_engine, so the engine and the Rust standard library from the toolchain travel beside it. A toolchain without them (an older release, a static build behind --lib-dir) still packages - its runtime library needs nothing beside it - unless the app declares [dependencies], which exits 1 naming the gap, because runtime modules need the shared engine.

The candela standard library travels in a libs/ subfolder of the package, whatever the kind and whatever links the engine: scripts read it off disk as they compile, so a package without it fails every import "std/..." and every array method at startup. It comes from wherever that package's engine came from. A Rust app takes the copy its own cargo build staged, beside the engine library that build produced. Every other kind takes the toolchain's, from the directory beside lumenc, from --lib-dir, or from the release archive for a --target. A source holding no libs/ warns and packages without it, since an app importing nothing from the library needs none of it.

--static writes the same folder with one executable in it: no runtime library, no shared engine, no libstd, and no modules/, because all of that is inside the file. libs/ still travels. It links the app from the link kit published for the target, so it needs a linker on this machine - a C toolchain on Linux, the Xcode Command Line Tools on macOS, the Visual Studio Build Tools and Windows SDK on Windows - and says which one is missing when the link cannot start. Exits 2 for a request it cannot answer: an SDK app (those bring their own executable), a --target other than this machine's platform (the link runs through the tools installed here), or a path or version module (only the toolchain's own bundled modules are in the kit). A module the kit does not carry exits 1 naming what it does carry, and so does a [capabilities] key the kit does not carry. The executable holds the optional subsystems the app's sources show it uses plus whatever [capabilities] asks for, and none of the rest; the summary line names them.

Declared [dependencies] stage into a modules/ subfolder of the package, each under the file name the runtime probes for. path sources copy the declared library, bundled sources copy the toolchain's, and version sources come from the registry; a module that cannot be found or resolved exits 1. A candela package stages nothing: its scripts compiled into the app's executable along with the app's own.

A --target for another platform resolves for that platform, so a version module ships the target's build. bundled modules come from the lumen-modules-<target> archive published with the same release the toolchain files come from, fetched, verified, and cached the same way; a release that ships no modules archive, or one whose archive does not carry a declared module, exits 1 naming it. A path source cannot cross-package - a local library is built for one platform - and exits 2. A Windows target with a non-empty [dependencies] exits 2: nothing loads a module beside a Windows executable, and --static is what compiles one in there.

The launcher stub and the runtime library are looked up in this order: --lib-dir, then, for this machine's own platform, the directory holding the running lumenc and then $LUMEN_LIB_DIR, then the download cache. When none of them has both files and the target is another platform, the archive for that platform is downloaded from the release this toolchain uses, checked against the sha256sums.txt published with it, and unpacked into the cache; a release that publishes no checksum for the archive, or no launcher in it, exits 1 rather than installing anything. When nothing can be found and nothing can be fetched, the error names every directory it looked in and why the download could not happen.

Windows and Linux packages carry the compiled app appended to the executable. A macOS package built on macOS links it in as a Mach-O section, which needs cc from the Xcode Command Line Tools; a macOS package built anywhere else ships it as <name>.lmna beside the executable instead.

Runs the app's prebuild hooks first unless --no-hooks is given. An <app_dir> that is not a directory, an extra positional argument, or an output directory that is the app directory itself, exits 2. A failed build, or a build that produced no executable, exits 1.

A packaged app accepts --headless [--ticks N], which runs it window-free for N ticks and exits; every other argument is left to the app.

bundle

lumenc bundle <app_dir> <out.lpak> [--no-hooks]
lumenc bundle --static <app_dir> <out_dir> [--no-hooks]

Without --static, packs every regular file under <app_dir> into a single .lpak archive, skipping dotfiles, target/, and the app's src/, and prints the file count. An archive holds assets; the code is compiled rather than looked up by name. Entries are keyed by their path relative to <app_dir>. Run against the archive with lumenc run <app_dir> --assets <out.lpak>.

With --static, resolves the app's capability set from [capabilities] plus a source scan, maps it to a cargo feature list, builds the trimmed runtime library with only those subsystems, and copies the result into <out_dir>. It prints each resolved capability and the feature list. This needs the Lumen source tree; set LUMEN_WORKSPACE_DIR to point at it. An app that declares [dependencies] gets a warning naming the declared modules: a static build compiles the engine into the binary, and engine-locked modules load only against the dynamically linked engine, so the bundled app runs without them.

Runs the app's prebuild hooks first unless --no-hooks is given. A missing argument or an extra positional argument exits 2.

new

lumenc new <name> [template]
lumenc new --list

Scaffolds a directory <name> from a template. The template argument is optional and defaults to blank. Every template writes lumen.toml and a README at the app root and the app's code under src/, starting with src/main.lmn.

Template Contents
blank A bare <root> and a lumen.toml.
hello One label and a script.
counter Buttons, bind-text, per-id click routing. Scripted in candela.
form Input, toggle, slider, live status line.
todo List, input, <for> loop, array signals.
dashboard Stat tiles, progress bars, activity feed driven by a timer.
settings Checkbox, radio, dropdown, and slider groups with derive().
hotkeys Global hotkeys, tray icon, OS notifications.

The scripted templates use candela, except dashboard, which is Lua, and hotkeys, which is Rhai. blank ships no script.

--list (or -l) prints the gallery with one-line descriptions and exits 0. An existing <name> exits 1 without writing anything. An unknown template exits 2 and names the available set.

The gallery is part of lumenc, so --list answers on any copy. The files are installed beside it, so scaffolding wants an installed toolchain: a copy without them writes nothing, exits 1, and names the directories it looked in. See Templates.

fmt

lumenc fmt <file> [--check]

Reformats a .lmn file in place and prints lumenc fmt: rewrote <file> when the bytes changed. With --check nothing is written; the command exits 1 when the file is not formatted and 0 when it is. A missing file argument or an unknown flag exits 2.

i18n extract

lumenc i18n extract <app_dir> [--lang <tag>]

Scans .lmn, .rhai, .lua, and .cdl files under <app_dir> for translation keys and writes <app_dir>/locale/<tag>.ftl. --lang defaults to the app's [app] fallback_locale, else en-US, and also accepts --lang=<tag>.

Recognised call shapes: t("key") and tr("key") (including candela's lumen::t("key")), t!(i18n, "key", ...) and tr!(i18n, "key", ...), and the translatable="key" markup attribute. A marked element also yields key.placeholder and key.alt when it writes those attributes, and yields the bare key for its own text. Keys built at runtime are invisible to the scan.

A key containing a dot names a Fluent attribute: everything before the first dot is the message, everything after is the attribute, and the extractor writes it as an indented .attr = line under that message. This holds for a key a script passes to t() as much as for one the markup derives.

The extractor is idempotent: existing entries are preserved verbatim, a message the file lacks is appended whole, and a message it has gains only the attribute lines it is missing. target, node_modules, .git, and locale directories are skipped. The command prints the total and new key counts.

lumenc i18n with no subcommand, or any subcommand other than extract, exits 2.

completions

lumenc completions bash|zsh|fish

Prints that shell's completion script on stdout. The scripts complete subcommand names, each subcommand's flags, the fixed value sets (--profile modes, --target platforms, new templates, --button names), and app directories or files where a subcommand takes one.

A missing shell argument, an unknown shell, or an extra argument exits 2.

Redirect the output to where your shell looks for completions:

lumenc completions bash > ~/.local/share/bash-completion/completions/lumenc
lumenc completions zsh > ~/.zsh/completions/_lumenc
lumenc completions fish > ~/.config/fish/completions/lumenc.fish

The zsh directory has to be on $fpath, above compinit:

fpath=(~/.zsh/completions $fpath)

A release archive ships the same three scripts, so an install from install.sh already has them on disk under the prefix:

Shell Path under the install prefix Setup
bash share/bash-completion/completions/lumenc source it from your bash rc file.
zsh share/zsh/site-functions/_lumenc Put the directory on $fpath.
fish share/fish/vendor_completions.d/lumenc.fish Link it into ~/.config/fish/completions/.

The installer prints the line for your shell after it runs. The Windows installer and the portable zip carry no completion scripts; write them out with lumenc completions there.

Automation commands

These drive an already-running app over its JSON-RPC TCP server. Each opens a connection, sends one request, prints the reply, and exits. Start the app first, in another shell or in the background. See Testing for a worked example.

Every command in this group accepts --port <n> and --app <dir>.

Port resolution

In order, first match wins:

  1. --port <n>
  2. LUMEN_MCP_PORT
  3. [mcp] port in <dir>/lumen.toml, when --app <dir> is given
  4. 7878

The connect timeout is one second and the read timeout five seconds. A connection failure exits 1 with a hint that the app may not be running.

snapshot

lumenc snapshot [--text|--json] [--max-lines N] [--cursor C]
                [--include-invisible] [--port P] [--app <dir>]

Prints an accessibility-tree-style text dump of the live UI. --text is the default; --json prints the raw result. --max-lines truncates and prints a cursor to resume from; pass it back with --cursor. --include-invisible (also spelled --no-omit-invisible) keeps entities that are not visible. Exits 0 on any successful call.

find

lumenc find [--text S] [--role R] [--id N] [--limit N] [--json]
            [--port P] [--app <dir>]

Searches the live snapshot. Prints one row per hit: id, role, label, position, size, state. Exits 1 with no matches when nothing matches.

element-at

lumenc element-at <x> <y> [--json] [--port P] [--app <dir>]

Prints the topmost entity at the logical-pixel point. Exits 1 on a miss.

click

lumenc click <x> <y> [--button primary|secondary|middle] [--wait-for R]
             [--json] [--port P] [--app <dir>]

Injects a click at the logical-pixel point. --wait-for names a message ring to wait on before returning, for example ClickEvent. Requires [mcp] simulate = true; without it the command exits 1 and prints the hint.

type

lumenc type <text> [--wait-for R] [--json] [--port P] [--app <dir>]

Types a string into the focused entity.

key

lumenc key <name> [--shift] [--ctrl] [--alt] [--super] [--wait-for R]
           [--json] [--port P] [--app <dir>]

Injects one key press, for example Enter, Tab, Escape, or a. --cmd is an alias for --super.

scroll

lumenc scroll <x> <y> <dx> <dy> [--wait-for R] [--json]
              [--port P] [--app <dir>]

Injects a wheel event of (dx, dy) pixels at the logical-pixel point.

lint

lumenc lint [--json] [--port P] [--app <dir>]
lumenc lint --css-cascade [<dir>] [--json]
lumenc lint --signals [<app-dir>] [--json] [--strict]

Plain lumenc lint queries the running app and prints one finding per line as <severity> <entity> <category>: <hint>. It exits 1 when any finding has error severity.

--css-cascade is offline: it parses <dir>/src/main.css and reports every rule whose resolved value differs between first-wins and last-wins cascade ordering. It exits 1 when it finds any divergence, and 0 when the app has no stylesheet.

--signals is offline: it reads <app-dir>/src/main.lmn, the app script (src/main.cdl, src/main.rhai, or src/main.lua), and the optional [signals] schema. Findings are printed as <severity> <file>:<line>:<col> [<kind>] <signal>: <message> with an optional hint line. Kinds: untyped-write, schema-mismatch, bare-interpolation, untracked-signal, orphan-write. --strict upgrades warnings to errors. Exits 1 when any finding is an error.

Both offline modes take the directory either positionally right after the flag or via --app, and default to ..

diff

lumenc diff [tick] [--json] [--port P] [--app <dir>]

Prints entity ids added, removed, and changed since tick, or since the previous tick when omitted.

screenshot

lumenc screenshot [out.png] [--highlight id1,id2,...] [--lint]
                  [--bounds map.json] [--port P] [--app <dir>]

Captures the app to a PNG, defaulting to lumen-screenshot.png. --highlight outlines the listed entity ids; --lint outlines every lint finding. --bounds also writes the entity bounds map as JSON. Prints the output path and pixel size. A non-integer in --highlight exits 2; an unavailable capture exits 1.

Update check

An installed lumenc looks for a newer release at most once a day and prints one line on stderr when it finds one. On a terminal it then offers to install it: the shell installer on Linux and macOS, the .msi on Windows.

The check runs only for run, check, build, bundle, new, fmt, and i18n. It is skipped when any of these hold:

  • The command line contains --headless.
  • LUMEN_NO_UPDATE_CHECK is set to a non-empty value.
  • CI is set.
  • stderr is not a terminal.
  • The copy is not an installed one (a build from source has no install receipt, and neither does the portable Windows zip or an install from Homebrew or Scoop).
  • The install is pinned, which install.sh --version records. An MSI install is never pinned.

The check never changes the command's exit code.

Which release toolchain files come from

package --target, package --static, and web download files that were published with a release: another platform's launcher stub and runtime library, a platform's link kit (lumen-linkkit-<target>.tar.gz), and the browser runtime. Which release they come from is read from the releases page, never from the version lumenc --version prints. A version number on its own says what a copy of lumenc is, and a copy can be newer than anything published.

Two things answer, in this order:

  1. An installed toolchain uses the release it was installed from, which its install receipt records. That is also what holds an install.sh --version pin in place: a pinned toolchain keeps downloading the files published with the version it was pinned to.
  2. Any other copy, such as a build from source or an unpacked portable zip, uses the newest published release. <repo>/releases/latest redirects to it. The answer is remembered for a day under the user cache directory, so repeated builds make one request and a machine that goes offline keeps building.

When a copy resolves to a release it is not, the build says which release the files come from.

Downloads are cached per release and per component under the platform cache directory (~/.cache/lumen/toolchain/<release>/<component> on Linux), so resolving a different release downloads into a different directory and never reuses files from another one. The component is the target name for a platform's toolchain files, linkkit-<target> for its link kit, and web for the browser runtime. Old directories are left in place; they are a cache and can be deleted at any time.

A repository that has published no releases and a releases page that cannot be reached both fail the lookup, and the error says which of the two happened. Neither one guesses a download address. Set LUMEN_GH_REPO to read releases from a different repository; it changes both the release these downloads come from and the one the update check compares against.

Environment variables

Variable Effect
LUMEN_MCP_PORT Port the automation commands connect to, below --port and above lumen.toml.
LUMEN_NO_UPDATE_CHECK Any non-empty value turns the update check off.
CI Turns the update check off.
LUMEN_THREADS Worker-thread budget. Overrides [runtime] threads.
LUMEN_DEVTOOLS_OPEN Any non-empty value other than 0 opens the devtools overlay at startup instead of waiting for F12.
LUMEN_HOT_RELOAD_POLL Forces the hot-reload watcher onto mtime polling instead of filesystem events.
LUMEN_FONT_CACHE 0, off, false, or no disables the persistent font-metadata cache and rescans system fonts every launch.
LUMEN_BOOT_TRACE Prints a phase-by-phase startup breakdown on stderr.
LUMEN_GPU_INIT_TRACE Prints GPU adapter and device selection detail on stderr.
LUMEN_GPU_INIT_DEADLINE_MS GPU init deadline in milliseconds; defaults to 5000. Exceeding it aborts with a diagnostic instead of hanging.
LUMEN_TRACE_FRAME_DIRTY Logs which source marked each frame dirty.
LUMEN_WORKSPACE_DIR Lumen source tree that bundle --static builds the trimmed runtime from.
LUMEN_LIB_DIR Directory searched for the shared Lumen library and the launcher stub, after the directory holding lumenc.
LUMEN_LINK_KIT_DIR Link kit package --static replays, instead of the one published for the target.
LUMEN_GH_REPO Repository, as owner/name, whose releases toolchain downloads and the update check read. Defaults to lumen-fx/lumen.
LPM_BIN The lpm executable to resolve registry packages with. Searched first, ahead of PATH and the shared install path.
LPM_GH_REPO Repository, as owner/name, lpm is downloaded from. Defaults to lumen-fx/registry.
LPM_ASSET_BASE Directory URL holding the lpm archives and their checksums.txt, instead of the newest release. For a mirror.