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.
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¶
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¶
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¶
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¶
Deletes the package's [dependencies] entry or its [[plugins]] entry and
re-resolves what is left. A name neither table declares exits 1.
fetch¶
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¶
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:
staticwrites 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.ssrwrites what a render needs and no documents: a page is produced when it is asked for, by running the app for that request.--serverenders them here; without it the directory is for a server you build onlumen-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¶
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¶
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¶
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¶
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:
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:
--port <n>LUMEN_MCP_PORT[mcp] portin<dir>/lumen.toml, when--app <dir>is given7878
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¶
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¶
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¶
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¶
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¶
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_CHECKis set to a non-empty value.CIis 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 --versionrecords. 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:
- An installed toolchain uses the release it was installed from, which its
install receipt records. That is also what holds an
install.sh --versionpin in place: a pinned toolchain keeps downloading the files published with the version it was pinned to. - Any other copy, such as a build from source or an unpacked portable zip,
uses the newest published release.
<repo>/releases/latestredirects 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. |