Reference
CLI
The lumiose command: every subcommand with its options, JSON output and exit codes.
The command comes with the @lumiose/cli package (pnpm add -D @lumiose/cli).
lumiose <command> [options]
lumiose <command> --help # usage, options, exit codes and examples
lumiose help --format json # every command, for agents#Output contract
- Exit codes:
0success,1problems found (errors, unformatted files, nothing found, a failed build, a server that could not start),2the tool itself failed or was misused (unknown command or option, missing tsconfig or oxlint, a project root that does not exist). Each command's--helplists its exit codes. --format json: stdout carries one JSON document,{ "command": "<name>", "ok": <bool>, ... }.okistrueexactly when the exit code is0. When the command could not do its job and no diagnostics explain why,failure: { message, help? }says so. Fields are only ever added. Usage errors are reported the same way (lumiose chek --format jsonprints{ "command": "chek", "ok": false, "failure": {...}, "suggestions": ["check"] }).- Logs go to stderr with
--format json, so stdout parses as JSON. - Typos get suggestions:
lumiose chekprintsDid you mean "lumiose check"?.
| Option | Applies to | Meaning |
|---|---|---|
--root <dir>, --cwd |
all | Project root (default: the current directory). |
--format human | json |
all | Output format (default human). |
-h, --help |
all | Help for the command. |
-v, --version |
The version ({ command: "version", ok, version } in JSON). |
|
-p, --project <file> |
check | tsconfig path relative to the root (default tsconfig.json). |
--check |
fmt | List unformatted files and exit 1 instead of writing. |
--port <n> |
dev, preview | Port; 0 picks a free one. |
--host <addr> |
dev, preview | Listen address (0.0.0.0: all addresses). |
#lumiose dev
Starts the Vite dev server with SSR and HMR, until interrupted.
What an edit updates: a stylesheet or a file's <style> block updates in place; an edited
component in an island's own module re-renders its instances in place (the page, its scroll
position, the island's other components and the other islands keep their state; see
hot updates); an island module the compiler could not register re-renders the
island's instances from their props. An island module that other client modules import reloads
the page, since they would keep its old exports, and so does other client code (a module an
island or an SPA route imports).
Server-only code (pages, layouts and components that no island or SPA route imports, middleware,
the document) updates the page in place: the page fetches its URL again and patches its DOM to the
new HTML. Islands keep their DOM and state, and focus, scroll positions and form input survive; the
title, meta tags and stylesheets of the head follow the new page. The page reloads instead when
the new HTML changes the <html>, <head> or <body> attributes, adds or removes a script, or
changes an island's props, hydration strategy or slot content; when the response status changed,
redirected or is not HTML; when the page was the result of a form post; and when the render
reported errors (the reload shows the overlay).
lumiose dev --port 3000
lumiose dev --format json # one line once listening: { "command": "dev", "ok": true, "url": "http://localhost:5173/", "urls": { "local": [...], "network": [...] } }Like Vite, the server stops when its stdin ends; start it with an open stdin when it runs in the background.
#lumiose build
Builds the client and server bundles into dist/, then prerenders the static routes and the spa
pages with prerender. When every route is prerendered (no server route, no spa route rendered
per request, no form action), the build
removes dist/server: dist/client is the whole site, and serverEntry is null.
lumiose({ headers }) is written to dist/client/_headers.
Route modules' chunks and stylesheets are named after their route folder, since every page is
page.stsx: blog/[slug]/page.stsx builds to assets/blog-slug-page-[hash].js (an spa page) and
assets/blog-slug-page-[hash].css, (app)/layout.stsx to assets/app-layout-[hash].css. A Vite
config that sets its own entryFileNames, chunkFileNames or assetFileNames keeps its names.
Servers and deploy adapters cache exactly the files the build named by content hash for a year;
files you put in public/assets/ revalidate
(@lumiose/adapter-node).
lumiose build --format json # { outDir, clientDir, serverEntry, prerendered: [{ path, file, kind }] }Route errors (LUM5002, LUM5003, LUM5005, LUM5006) and compile errors exit 1.
#lumiose preview
Serves the build output until interrupted. With dist/server, it serves it with
@lumiose/adapter-node (a dependency of the project), and the app runs with NODE_ENV=production
unless NODE_ENV is set. Without it (every route prerendered), it serves
dist/client as a static site, no adapter needed: page URLs map to about/index.html or
about.html, and unknown URLs get 404.html with status 404, as static hosts do. Every response,
static files included, gets lumiose({ headers }) and Vite's preview.headers, which default to
server.headers (as with vite preview)
(adapter headers).
--format json prints one line once listening: { "command": "preview", "ok": true, "url" }.
#lumiose check
Type-checks the project: runs typegen (.lumiose/types/routes.d.ts), writes shadow TSX files for
every .stsx (.lumiose/shadow/; also those of used Lumiose packages when the tsconfig extends
.lumiose/tsconfig.generated.json, as the template does, see
tsconfig), writes the generated
tsconfig, runs tsc --noEmit -p tsconfig.json, maps shadow positions back
to the .stsx sources (same line and UTF-16 column), and merges scanner, route, tsconfig and
compiler diagnostics for .stsx and .tsx files (such as LUM1006 for a <style> block in a
.tsx file). For style diagnostics, the modules that $ constants are imported from are resolved
like TypeScript resolves them (relative paths, tsconfig paths, package exports). Route
diagnostics use the project's default mode from lumiose({ mode }).
lumiose check
lumiose check --format json # { command, ok, diagnostics: [...], summary: { errors, warnings, hints, files } }Each JSON diagnostic has source (ts, lumiose, oxlint, stylelint), code, severity,
file, line, column, endLine, endColumn, message, and optional help, docs and
fix. A fix is { message, edits: [{ line, column, endLine, endColumn, text }] }, with the same
positions as diagnostics; apply all its edits together.
TypeScript errors inside a raw-CSS <style> block of a .tsx file are dropped: LUM1006 reports
the block, and renaming the file to .stsx removes them. TypeScript errors on an island call site
for a prop that cannot be serialized are reported as LUM4002 on the prop (see
LUM4002). While the project has syntax errors, TypeScript reports no type
errors yet: the report then ends with a note (JSON: notes: [...]), and another run after the
fixes shows them.
#lumiose typegen
Writes the route types (.lumiose/types/routes.d.ts), the generated tsconfig
(.lumiose/tsconfig.generated.json) and syncs the shadow TSX files (.lumiose/shadow/), without
type-checking: what a bare tsc or an editor needs.
lumiose typegen --format json # { routeTypes: { file, changed }, routes, shadows: { dir, files, written, deleted }, diagnostics, summary }#lumiose routes
Lists the routes in match order, with their kind, mode (from the route, its layouts or the project
default lumiose({ mode })) and layouts. spa (shell) marks a spa page with ssr = false, and
spa (prerendered) a spa page rendered to HTML at build time, by its own export const prerender,
a layout's or lumiose({ prerender }).
With lumiose({ i18n }), the header names the locales and a LOCALES column lists the locales
each route declares (its own locales export, a layout's, or the default locale alone);
locales() in <file> marks a route whose locales({ params }) function decides per params.
$ lumiose routes
Routes in src/routes (default mode: static): 2 pages, 1 endpoint
PATTERN KIND MODE FILE LAYOUTS
/ page static page.stsx layout.stsx
/api/search endpoint server api/search/route.ts
/blog/[slug] page server blog/[slug]/page.stsx layout.stsx, blog/layout.stsx
$ lumiose routes # with lumiose({ i18n })
Routes in src/routes (default mode: static, locales: en, es, fr (default en)): 3 pages, 0 endpoints
PATTERN KIND MODE FILE LOCALES LAYOUTS
/ page static page.stsx en, es, fr
/about page static about/page.stsx en, es
/blog/[slug] page static blog/[slug]/page.stsx locales() in blog/[slug]/page.stsx
$ lumiose routes --format json # { routesDir, defaultMode, i18n?, routes: [{ pattern, file, kind, mode, ssr?, prerender?, layouts, params, locales?, localesFrom? }], diagnostics, summary }Paths in JSON are project-relative. Exits 1 when the routes have errors.
#lumiose lint
Runs oxlint on the project, including .stsx files (through their shadow TSX, with diagnostics
inside style blocks dropped), and, when the project installs and configures stylelint, on each
block's CSS with $ references replaced by same-length identifiers. The JSON report has the shape
of lumiose check's.
#lumiose fmt
lumiose fmt [paths...] # default path: src
lumiose fmt --check
lumiose fmt --check --format json # { check, files: [{ file, status, warnings, error? }], summary: { files, formatted, errors } }Formats .stsx files: the TSX with oxfmt (each block replaced by a placeholder), then each block's
CSS separately with its $ references protected, then re-indents and stitches the file together.
With --check, a formatted file is one that is not formatted yet.
#oxlint, ESLint and Prettier
Projects that run the tools themselves add Lumiose through plugins. The lint rules are thin
adapters over the compiler: they report its LUMxxxx diagnostics (with help, the docs topic and
fixes), so every tool agrees with lumiose check. Project-wide checks (LUM4003, LUM3012) and the
style diagnostics of $ constants imported from other modules need the whole project: run
lumiose check for those.
oxlint (JS plugins, alpha in oxlint): lints .tsx files; oxlint cannot parse .stsx yet, so
lumiose lint covers those.
// .oxlintrc.json
{
"jsPlugins": ["@lumiose/eslint-plugin"],
"rules": { "lumiose/compiler": "error", "lumiose/compiler-warnings": "warn" }
}ESLint 9 (flat config): the recommended config also lints .stsx files through a processor
that hands ESLint the masked TSX (same offsets, so positions and fixes apply to the source). Parse
TSX with a TypeScript parser, such as typescript-eslint's.
// eslint.config.js
import lumiose from "@lumiose/eslint-plugin";
import tseslint from "typescript-eslint";
export default [
...lumiose.configs.recommended,
{ files: ["**/*.tsx", "**/*.stsx/*.tsx"], languageOptions: { parser: tseslint.parser } },
];Prettier: @lumiose/prettier-plugin formats .stsx like lumiose fmt (the TSX and each block's
CSS with Prettier's printers, re-indented at their placement), following Prettier's options; a
file with scanner or syntax errors is reported, not changed. oxfmt has no plugin API, so
lumiose fmt remains the way to format .stsx with oxfmt.
// .prettierrc.json
{ "plugins": ["@lumiose/prettier-plugin"] }#lumiose docs
lumiose docs # the docs index
lumiose docs signals # a topic by name...
lumiose docs concepts/signals # ...or by path
lumiose docs deploy # a section's index (deploy/README), like errors
lumiose docs LUM2001 # a diagnostic page (also errors/LUM2001, diagnostics/LUM2001)
lumiose docs fetchJSON # an exported name: its section of the API reference
lumiose docs bind:value # no topic matches: search, best matches first
lumiose docs --format json # { topics: [{ topic, title, summary }] }
lumiose docs signals --format json # { topic, title, summary, related, content }
lumiose docs Link --format json # { topic, heading, content }
lumiose docs "await island" --format json # { query, results: [{ topic, title, score, snippet }] }Prints the version-matched docs from node_modules/lumiose/docs. A bare name prefers concepts over
reference pages and lists the other matches. An exported name (case-sensitive, such as signal,
Link or fail) prints the section of the reference page that documents it. A search requires
every term to occur in a page.
Exits 1 when nothing matches.
#lumiose explain
lumiose explain LUM3010
lumiose explain lum3010 --format json # { code, title, severity, category, topic, content }Prints the long-form explanation of a diagnostic code: what it means, an example, and how to fix it. Exits 1 for an unknown code and 2 for text that is not a code.
#lumiose agents init
lumiose agents init
lumiose agents init --mcp # also .mcp.json (Claude Code) and .codex/config.toml (Codex)
lumiose agents init --format json # { files: [{ file, action: "created" | "updated" | "unchanged" }] }Makes an existing project agent-ready, as create-lumiose does for new ones: a Lumiose section in
AGENTS.md (between and, pointing at
node_modules/lumiose/docs, the .stsx format, the key rules and lumiose check), a CLAUDE.md
that references @AGENTS.md, the lumiose skill (.claude/skills/lumiose/SKILL.md) and the
*.stsx linguist-language=TSX line in .gitattributes, and a tsconfig.json that extends
.lumiose/tsconfig.generated.json when the project has none (an existing one is left alone).
Re-running it updates only those parts. --mcp also registers lumiose mcp for Claude Code and
Codex, run with the package manager of the lockfile (see MCP); the JSON
report then has mcp: { command, args, note }.
#lumiose mcp
lumiose mcp
claude mcp add lumiose -- npx lumiose mcp # register it with Claude CodeStarts the MCP server on stdio for coding agents: the tools docs_search, docs_get, check,
routes, components, compile, render and dev_logs, and the docs and the route manifest as
resources. It runs until the client disconnects. See MCP server.