CLI overview
One-shot compiler commands, saved-input watch checks, and stdio editor diagnostics.
effx check, build, inspect, and graph run the same compiler pipeline over a tsconfig project.
dev repeats checks for saved inputs; lsp checks source buffers for one editor project.
The long-running commands use the same compiler meaning and add explicit lifetime and executable-coverage policy.
lift reads pure Effect source instead and prints suggested declarations; only its explicit --check runs application modules.
Source: scripts/effx.ts, packages/cli/src/commands.ts, docs/specs/0015-config-and-extensions.md.
The CLI is not published. Run it from the monorepo: bun run effx <command> at the repository root (root script effx is bun scripts/effx.ts). See Getting started.
Commands
| Command | Description (from main.ts) | Writes files | Page |
|---|---|---|---|
effx check | Diagnose without writing | no | check |
effx build | Write selected projections (default: all) | .effx/ir.json, .effx/manifest.json, .effx/surface.json, generated files | build |
effx dev [--build] | Check saved inputs until stopped; build only with explicit write intent | no by default; normal build artifacts with --build | dev |
effx lsp | Stdio diagnostics for one selected project and unsaved source buffers | no compiler artifacts | lsp |
effx inspect <name> | Show one operation's contract and exposures | no | inspect |
effx graph [name] | Print a Mermaid graph, optionally from one node | no | graph |
effx lift --group <id> [--module <file>] [--form verbose|dense|both] [--check] [--emit-patch] [--write <file>] [--json] | Suggest effx declarations for a pure Effect HttpApiGroup; --check mechanically verifies them against the original group (spec 0019) | --write <file> creates one new suggestion file; nothing else | lift |
effx surface check --against <file> | Verify an Alchemy Worker program wires exactly the generated handlers the surface requires (spec 0021). Compiles the project first and exits 1 on errors. | no | none yet |
effx cedar [--namespace <Name>] [--policies <file>]... [--deny-warnings] | Project capabilities and access contracts to a Cedar schema and policies, validated by Cedar 4.13.0 (spec 0017). Separate from build: nothing else under .effx changes. | .effx/cedar/schema.cedarschema, .effx/cedar/policies.cedar | none yet |
The root command is described as "AOT application compiler for Effect".
Source for the surface check row: main.ts description and packages/cli/src/surface.ts (surfaceCheck, wiringDiagnostics). The comment there states that it never executes the program. Its diagnostic codes and the wiring rules are not described on this page.
effx cedar (packages/cli/src/cedar.ts, docs/specs/0017-effx-cedar.md) projects only; it evaluates no request and issues no lease. It is an opt-in command: check and build never run it or load its validator. Its validator, @cedar-policy/cedar-wasm 4.13.0, is an optional peer dependency of @effx/cli; install it with bun add @cedar-policy/cedar-wasm@4.13.0, otherwise the command fails naming that install. For this command --out-dir is the Cedar output directory (default <tsconfig dir>/.effx/cedar) and --strict-access is not applied. --namespace defaults to Effx. --policies validates application-written .cedar files against the emitted schema (EFFX4102, EFFX4106). The command's own codes are EFFX4101 to EFFX4107; exit status is 1 when any error is reported, or any warning with --deny-warnings.
Flags
The shared compilation flags below retain the same precedence for one-shot commands and sessions.
dev adds --build; lsp adds launch-time --trust-config.
Both accept repeatable --exec-file and --exec-dir inputs.
See dev coverage and LSP trust before you use executable config.
| Flag | Type | Applies to | Precedence | Default when nothing sets it |
|---|---|---|---|---|
--project | string (path to a tsconfig) | check, build, inspect, graph | Highest. Beats the config project. | tsconfig.json |
--config | string (path to a config module) | all four | Highest. Beats discovery. | effx.config.ts beside the selected tsconfig, if it exists; otherwise no config |
--out-dir | string (path) | all four | Highest. Beats config outDir and the tsconfig effx.outDir. | <tsconfig dir>/.effx/generated |
--strict-access / --no-strict-access | boolean | all four | Highest. An explicit --no-strict-access beats a config or tsconfig true. | false |
--target | effect-4.0 only | all four | Cannot override an unsupported installed Effect version. | resolved from the installed stable Effect version |
--emit | one of contract, handlers, all | all four | Highest. Beats config emit and tsconfig effx.emit. | all |
--naming-problem-identifier | string (declared pattern) | all compile commands | Highest. Beats config naming.problemIdentifier and tsconfig effx.naming.problemIdentifier. | <endpointKey>Problem |
inspect and graph accept all seven flags and resolve them exactly as check and build do. For example inspect User.Get --strict-access and graph --emit contract both exit 0 (observed by running bun scripts/effx.ts in examples/users). --strict-access changes diagnostic severity, and inspect and graph print no diagnostics, so it does not change their output. --naming-problem-identifier changes problem-contract semantics; see Problems and access for the pattern rules. check --strict-access on the same project turns the two EFFX2504 warnings into errors and exits 1, and check --no-strict-access keeps them as warnings and exits 0 (observed).
Positional arguments:
| Command | Argument | Required |
|---|---|---|
inspect | name | yes |
graph | name | no |
Literal values
The --target and --emit values come from the TargetProfile and EmitMode schemas in packages/compiler/src/Collected.ts. main.ts builds the flags from TargetProfile.literals and EmitMode.literals, so the CLI and compiler cannot disagree.
| Schema | Literals |
|---|---|
TargetProfile | effect-4.0 |
EmitMode | contract, handlers, all |
Both --emit=contract and --emit contract forms are exercised by the CLI tests. See Emit modes and targets for what the values mean.
Defaults when nothing sets a value
| Value | Result | Source |
|---|---|---|
| emit | all | emit ?? config?.emit ?? effx?.emit ?? "all" in resolveProject, packages/cli/src/commands.ts |
| target | The target project's installed stable Effect >=4.0.0 <5 selects effect-4.0. Prerelease, missing, or unsupported packages produce EFFX2701. | Spec 0010 stable-only amendment, packages/frontend-ts/src/project.ts |
| strict access | false. Access warnings do not block generation. | docs/specs/0006-access-contract-extension.md, resolveProject |
Project config
A project can keep its settings in an effx.config.ts module instead of repeating flags. The module default-exports defineConfig({ ... }) from @effx/cli/config. This page covers how the CLI finds and applies the file. For authoring extensions and the extensions and generators fields, see Extensions.
import { defineConfig } from "@effx/cli/config";
export default defineConfig({
project: "tsconfig.effx.json",
outDir: ".effx/generated",
emit: "contract",
target: "effect-4.0",
strictAccess: true,
naming: { problemIdentifier: "{Group}{Key}Problem" },
});project, outDir, emit, target, strictAccess and naming are optional (EffxConfig in packages/cli/src/config.ts). Naming is semantic input, unlike generator toggles: use the same pattern in both emit passes (spec 0024 §5).
Discovery
| Situation | Behaviour |
|---|---|
No --config, file present | The CLI loads effx.config.ts from the directory of the tsconfig selected by --project (or ./tsconfig.json). |
No --config, file absent | The defaults apply. No error. |
--config <path> | That file is loaded. The path resolves from the working directory. |
--config <path> and the file does not exist | The command fails before compilation with invalid effx config <file>: file does not exist. The --config nope.ts case exits 1 (observed). |
The module cannot be imported, has invalid fields, or has an invalid extensions value | The command fails before compilation with invalid effx config <file>: <reason> (loadConfig and configuredExtensions in commands.ts). |
Discovery looks beside the tsconfig selected by the flag or the default. If the discovered config sets another project, the CLI does not look for a second config beside that project. When --project is given, the config project is ignored.
The CLI evaluates the selected config and its imports, not application operation modules.
A long-running session retains that executable graph until restart.
LSP requires explicit --config or launch-time --trust-config before discovered config executes.
Coverage is conditional on complete caller declarations; it is not a sandbox.
Precedence
Each value takes the first source that sets it, from left to right.
| Value | CLI flag (highest) | Config | tsconfig effx block | Default |
|---|---|---|---|---|
| tsconfig | --project | project | none | tsconfig.json |
| config module | --config | discovery beside the selected tsconfig | none | absent |
| output directory | --out-dir | outDir | outDir | <tsconfig dir>/.effx/generated |
| emit mode | --emit | emit | emit | all |
| target | --target | target | target | installed Effect version |
| strict access | --strict-access or --no-strict-access | strictAccess | strictAccess | false |
| project root | none | none | projectRoot | tsconfig directory |
Source: the Resolution table in docs/specs/0015-config-and-extensions.md, checked against resolveProject in packages/cli/src/commands.ts. The spec's extra "programmatic ProjectConfig.projectRoot" source applies to library callers of compile, not to the CLI.
Where relative paths resolve
| Source of the path | Resolves from |
|---|---|
CLI flag (--project, --config, --out-dir) | the working directory |
config project, config outDir | the directory that contains the config file |
tsconfig effx block (outDir, projectRoot) | the directory that contains the tsconfig |
The .effx directory that holds ir.json, manifest.json and surface.json is <tsconfig dir>/.effx. --out-dir and outDir move only the generated files (effxDir in resolveProject).
Extensions and generators
The config can also set extensions and generators. These have no CLI flags. A config extensions array is appended to the built-in extensions. A callback receives the built-ins and returns the whole ordered list. generators toggles (http, rpc, cli, client, foldkit) switch off only the matching built-in file generator. All compilation paths use the resulting extension list. See Extensions for the authoring guide.
For sessions, executableCoverage supplies files and directories relative to the config.
Launch declarations resolve from startup cwd and merge by union while retaining logical aliases.
See Executable coverage for the exact shape, static package-link examples, and completeness limitation.
Exit behaviour
| Situation | Result |
|---|---|
check or build finds a diagnostic with severity error | Diagnostics are printed first. The command then fails with CheckFailed and exit code 1. |
inspect or graph name resolves to nothing | An error: line is printed on stdout. The command fails with UnknownName and exit code 1. |
Explicit --config file is missing or invalid | Fails before compilation with invalid effx config <file>: <reason>. Exit 1 for a missing file (observed). |
| IO or invariant breakage | The command fails with CompilerFault (a tagged error, not a diagnostic). |
Source: CheckFailed and UnknownName in packages/cli/src/commands.ts, which are marked errorReported = false so the runtime prints nothing more. The numeric exit code 1 for these two is stated in their doc comments and spec 0003. The exit code for CompilerFault is decided by BunRuntime.runMain and is not specified in the repository docs. A config failure is a CompilerFault built by invalidConfig with stage collect.
Invalid --target or --emit values fail at the CLI boundary, before the compiler runs (CLI test).
Output goes to stdout
One-shot diagnostics, the summary, inspect text, graph text, explain text and the lift report use stdout.
dev prints complete cycle batches. LSP stdout contains only framed JSON-RPC; operational output uses stderr or protocol logs.
Every command writes through the process's standard output and error streams and returns only after the stream accepted the bytes. Output larger than a pipe or socket buffer therefore reaches a slow reader complete, with the same bytes as a redirect to a file. Source: printLines in packages/cli/src/output.ts.
See Diagnostics for the shared diagnostic data.