effx
CLI

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

CommandDescription (from main.ts)Writes filesPage
effx checkDiagnose without writingnocheck
effx buildWrite selected projections (default: all).effx/ir.json, .effx/manifest.json, .effx/surface.json, generated filesbuild
effx dev [--build]Check saved inputs until stopped; build only with explicit write intentno by default; normal build artifacts with --builddev
effx lspStdio diagnostics for one selected project and unsaved source buffersno compiler artifactslsp
effx inspect <name>Show one operation's contract and exposuresnoinspect
effx graph [name]Print a Mermaid graph, optionally from one nodenograph
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 elselift
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.nonone 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.cedarnone 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.

FlagTypeApplies toPrecedenceDefault when nothing sets it
--projectstring (path to a tsconfig)check, build, inspect, graphHighest. Beats the config project.tsconfig.json
--configstring (path to a config module)all fourHighest. Beats discovery.effx.config.ts beside the selected tsconfig, if it exists; otherwise no config
--out-dirstring (path)all fourHighest. Beats config outDir and the tsconfig effx.outDir.<tsconfig dir>/.effx/generated
--strict-access / --no-strict-accessbooleanall fourHighest. An explicit --no-strict-access beats a config or tsconfig true.false
--targeteffect-4.0 onlyall fourCannot override an unsupported installed Effect version.resolved from the installed stable Effect version
--emitone of contract, handlers, allall fourHighest. Beats config emit and tsconfig effx.emit.all
--naming-problem-identifierstring (declared pattern)all compile commandsHighest. 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:

CommandArgumentRequired
inspectnameyes
graphnameno

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.

SchemaLiterals
TargetProfileeffect-4.0
EmitModecontract, 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

ValueResultSource
emitallemit ?? config?.emit ?? effx?.emit ?? "all" in resolveProject, packages/cli/src/commands.ts
targetThe 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 accessfalse. 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

SituationBehaviour
No --config, file presentThe CLI loads effx.config.ts from the directory of the tsconfig selected by --project (or ./tsconfig.json).
No --config, file absentThe defaults apply. No error.
--config <path>That file is loaded. The path resolves from the working directory.
--config <path> and the file does not existThe 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 valueThe 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.

ValueCLI flag (highest)Configtsconfig effx blockDefault
tsconfig--projectprojectnonetsconfig.json
config module--configdiscovery beside the selected tsconfignoneabsent
output directory--out-diroutDiroutDir<tsconfig dir>/.effx/generated
emit mode--emitemitemitall
target--targettargettargetinstalled Effect version
strict access--strict-access or --no-strict-accessstrictAccessstrictAccessfalse
project rootnonenoneprojectRoottsconfig 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 pathResolves from
CLI flag (--project, --config, --out-dir)the working directory
config project, config outDirthe 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

SituationResult
check or build finds a diagnostic with severity errorDiagnostics are printed first. The command then fails with CheckFailed and exit code 1.
inspect or graph name resolves to nothingAn error: line is printed on stdout. The command fails with UnknownName and exit code 1.
Explicit --config file is missing or invalidFails before compilation with invalid effx config <file>: <reason>. Exit 1 for a missing file (observed).
IO or invariant breakageThe 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.

On this page