CLI
Every chroma command, flag, environment variable, and exit behavior for scaffolding, serving, validating, and building apps.
The chroma command (chroma.sh on macOS and Linux, chroma.ps1 on Windows) scaffolds, serves, validates, and builds Chroma apps. For a first run from scratch, follow the Quick start. This page is the reference.
chroma build always produces the same dist/ layout for a given chroma.json; it does not emit separate "web" and "desktop" binaries. You choose Portable (Web) or Executable (Chroma Desktop) when you run the bundle (static host, browser tab, or chroma-desktop). The plugin list in chroma.json is copied into every build; hooks and public APIs are the same, but storage, origins, and some plugin internals follow the runtime mode.
| Command | Use it to |
|---|---|
chroma install | Provision ~/.chroma (once per machine, or after updating Chroma) |
chroma new <name> | Create an app from a template |
chroma validate | Check chroma.json and the project layout |
chroma dev | Serve the app locally while you edit |
chroma build | Produce dist/ (and optionally a .zip) |
chroma fs init, chroma fs link | Set up Chroma-Fs storage |
chroma add | Vendor a legacy library into the app |
chroma help, chroma --version | Print usage or the CLI version |
Install
Install the CLI from the installer package that Targ Apps provides to licensed customers (see the Quick start). Extract the package and run ./chroma.sh install (macOS/Linux) or .\chroma.ps1 install (Windows).
chroma install [--no-rebase-root]install copies the chroma command, the dev-server binary (engine), the templates, the compiled engine (pkg/), and the first-party plugins into $CHROMA_HOME, then adds $CHROMA_HOME/bin to your PATH. It also installs chroma-desktop when a binary or Chroma-Desktop source is available to it.
| Flag | Effect |
|---|---|
--no-rebase-root | Update in place. Without it, every run wipes $CHROMA_HOME and reinstalls from scratch, which is the safe way to repair a broken install. |
CHROMA_HOME defaults to ~/.chroma; export it before running install to change it.
~/.chroma/
├── bin/ # chroma, engine, chroma-desktop
├── engine-src/ # compiled engine (plus Rust source when installed from a full checkout)
├── plugins/ # first-party plugins
└── templates/ # static, dynamic, standardThe installer package installs the prebuilt engine; it does not install Node.js, Rust, or wasm-pack.
Commands
chroma new
chroma new <name> [--template static|dynamic|standard]Creates ./<name> from the chosen template, with a fresh pkg/, the plugins listed in the template's chroma.json vendored under plugins/, and the name fields rewritten to <name>. The default template is static. init is an alias for new.
It fails if <name> already exists, if the template is not one of the three names, or if chroma install has not run. Template differences are in Templates.
chroma validate
chroma validate [--strict] [--no-schema]Run it before dev or build. On macOS and Linux it runs two steps:
- JSON Schema:
chroma.json, andchroma-fs.jsonwhen present, against the schema catalog (needs Node.js andnpm; it installsajvinto a temporary folder on every run). The catalog is read from$CHROMA_SCHEMAS(a directory containingchroma/chroma.schema.json), theschemas/folder installed under$CHROMA_HOME, or the docs catalog when run inside the TargApps workspace. If none is found, the step is skipped with a warning. A schema failure exits immediately, before the structural step. - Structure:
nameexists andmodeisstaticordynamic;entryandpkg/chroma.js(orchroma.d.ts) exist; a dynamic app has an existingbuild.jsEntry; a static app'sbuild.scriptsfiles exist; eachplugins[]id has aplugins/<id>/folder. Legacy.jsfiles undersrc/produce a warning, as do a plugin withoutchroma.plugin.jsonand a static app with nobuild.scripts.
On Windows, chroma.ps1 validate takes no flags and runs only the structural checks (no schema step, no plugin-manifest warning, no build.scripts check).
| Flag | Effect |
|---|---|
--no-schema | Skip the JSON Schema step; run only the structural checks. |
--strict | Parsed and listed in --help (which says it treats warnings as errors), but it has no effect: nothing reads the flag after it is parsed, and warnings never change the exit code. |
On macOS and Linux any other option is an error. See chroma.json for the schema.
chroma dev
chroma dev [--port <n>] [--no-desktop] [--no-reload] [--reload]Serves the current directory with the installed dev server (engine) and requires a chroma.json. Static and dynamic apps are served the same way: files are loaded as native ES modules, with .chr served as JavaScript, so nothing is bundled in development.
| Flag | Effect |
|---|---|
--port <n> | Port to use. Default: dev.port from chroma.json, else 4173. |
--no-desktop | Serve in the browser only, even if dev.desktop is true. |
--no-reload, --no-hot-reload | Disable live reload for this session. |
--reload, --hot-reload | Enable live reload for this session. |
Unknown flags are ignored.
Desktop develop mode. For a dynamic app with dev.desktop: true (the dynamic and standard template default), chroma dev starts the dev server in the background and then runs chroma-desktop --develop --url http://127.0.0.1:<port>/<entry> --title <name>. The Desktop binary must be installed in $CHROMA_HOME/bin; if it is missing, the command stops with an error that tells you to run chroma install from a full workspace checkout or pass --no-desktop. Live reload is always off in this mode.
Ports. chroma dev uses the port above (the templates set 5173 for dynamic and 4173 for static). Chroma Desktop serves imported apps from a different, fixed loopback port; see Server.
Live reload switches (dev.hotReload, CHROMA_DEV_RELOAD, debounce) are documented once in Live reload.
dev.desktop only changes which host opens during development (browser vs Desktop develop window). It does not change chroma build output or which plugins are vendored. For a matrix of host-specific behavior, see Runtime modes.
chroma build
chroma buildPackages the app into outDir (default dist/), which is wiped on every run. What happens depends on mode:
static | dynamic | |
|---|---|---|
| Source | Flat HTML and JS files | ES modules under src/**/*.chr from one build.jsEntry |
| Scripts and styles | Each file in build.scripts and build.styles is minified as written (esbuild) | The module graph is bundled into one outfile (default bundle.js) with esbuild (--bundle --format=esm --minify, .chr loaded as JS) |
| WASM engine | Inlined as base64 into a minified chroma.js; no .wasm file ships | chroma_bg.wasm is copied next to the bundle; it must stay beside it |
| Engine rebuild | Never | If engine.rebuildWasm is true, the engine is recompiled from $CHROMA_HOME/engine-src first (needs a source-checkout install) |
package.zip | Not used | If true, dist/ is archived next to outDir, named after name |
The archive is <name>.zip (or .tar.gz when zip is not installed on macOS/Linux; always .zip on Windows). It is the bundle Chroma Desktop imports; see Desktop. Plugins, chroma.json, and chroma-fs.json are copied into the output.
Typical dynamic output:
dist/
├── index.html
├── bundle.js
├── chroma_bg.wasm
├── plugins/
└── chroma.jsonTo deploy dist/ on a static host, see Server. To import the zip into Chroma Desktop instead, see Desktop. Both paths use the same artifact; only the host changes (Runtime modes).
Compile pipeline
Three layers are involved when you ship a dynamic app:
| Layer | What compiles it | Output | Runtime mode |
|---|---|---|---|
| Engine | Rust → wasm-pack build --target web (via Chroma/build.sh or engine.rebuildWasm during chroma build) | pkg/chroma.js + chroma_bg.wasm (or WASM inlined for static) | Same WASM in Portable and Executable hosts |
| App | chroma build → esbuild (dynamic) or per-file minify (static) | dist/bundle.js (or minified scripts), index.html, copied plugins/ | Loaded by browser or Desktop iframe |
| Host | Not part of chroma build | Browser tab, static CDN, or chroma-desktop + optional .zip import | Chooses origins, reload, and Chroma-Fs bridges |
There is no separate chroma build --desktop flag today. Test Portable by serving dist/ (Server) and Executable by importing the zip (Desktop). Optional plugins such as Chroma-Style and Chroma-t2t ship in the same plugins/ folder regardless of host; per-plugin and per-tool matrices live in Extensions & plugins by mode and Runtime modes.
chroma fs init and chroma fs link
chroma fs init writes chroma-fs.json in the current app and fails if it already exists. The default backend is filesystem for dynamic apps and indexeddb for static apps.
chroma fs link builds Chroma-Fs from a workspace checkout and links the @chroma/fs package into the project (npm install file:...). It requires Node.js. The standard template exposes both as npm run fs:init and npm run fs:link. Storage options are in the Chroma-Fs docs.
chroma add
chroma add <lib-id> [--list] [--force] [--dry-run] [--registry <path>]Vendors a UMD or global-script library from the legacy-library registry into the current app (requires Node.js). First-party plugins such as chroma-fs and chroma-style come with chroma install and chroma new, not chroma add. Details: chroma add.
chroma help and chroma --version
chroma help (also --help, -h, or no arguments) prints usage. chroma --version (or -v) prints the CLI version.
Environment variables
| Variable | Default | Effect |
|---|---|---|
CHROMA_HOME | ~/.chroma | Install root. |
CHROMA_SCHEMAS | unset | Directory with the JSON Schema catalog used by validate. |
CHROMA_DEV_RELOAD | on for browser dev | See Live reload. |
CHROMA_LOG_LEVEL | log | Verbosity of the CLI and the dev server. |
Log levels
Command output looks like [Chroma] <pid> - <timestamp> LOG [Dev] Starting dev server on port 4173..., with a level and a context (Install, New, Dev, Build, Fs, Desktop, Validate, Add, Cli). Set CHROMA_LOG_LEVEL to one of these values (case-insensitive); each level also shows everything above it:
| Value | Shows | Stream |
|---|---|---|
error | ERROR | stderr |
warn | plus WARN | stderr |
log (default) | plus LOG | stdout |
debug | plus DEBUG | stdout |
verbose | plus VERBOSE | stdout |
An unknown value falls back to log. Colors apply only on a terminal.
Requirements
- Node.js (with
npx):chroma buildbundles and minifies with esbuild.chroma add,chroma fs link, and the schema step ofchroma validatealso use it. wasm-pack: thedynamicbuild checks for it onPATHand stops if it is missing, even whenengine.rebuildWasmisfalse.chroma installinstalls it only from a source checkout.- Rust: needed only to compile the engine (
engine.rebuildWasm: true), which requires an install from a source checkout.
The per-command table, including what the installer does and does not provision, is in the Quick start.
Troubleshooting
Every error below exits with status 1.
| Message | Cause and fix |
|---|---|
chroma.json not found in the current directory. | Run the command inside an app folder (one created by chroma new). |
'<name>' already exists in the current directory. | Pick another name for chroma new. |
Invalid template: '<x>' (use 'static', 'dynamic', or 'standard') | Use one of the three names. |
Chroma environment not found at $CHROMA_HOME. Run 'chroma install' first | Run chroma install. |
Compiled engine not found at .../pkg. Run 'chroma install' first. | Partial install; run chroma install again (it wipes and rebuilds by default). |
engine binary not found at .../bin/engine. Run 'chroma install'. | Run chroma install again. |
Node.js is required for this step (bundling/minifying). | Install Node.js from nodejs.org, then retry chroma build. |
wasm-pack not found. Run 'chroma install'. | Dynamic build without wasm-pack on PATH. Install it, or reinstall from a source checkout. |
chroma-desktop not found. ... | Desktop develop mode without the binary. Run chroma dev --no-desktop, or install Chroma Desktop. |
Unknown mode in chroma.json: '<x>' | mode must be static or dynamic. |
Next
- Quick start
- Runtime modes — Portable (web) vs Executable (Desktop) after
chroma build - chroma.json
- Server
- Templates
- Desktop