---
title: "The hem CLI"
description: "Every hem command, its options, its file selection behavior, and its exit conditions."
image: "https://hem.run/social/hem.png"
---

> Documentation Index
> Fetch the complete documentation index at: https://hem.run/llms.txt
> Use this file to discover all available pages before exploring further.

# The hem CLI

Running `hem` with no arguments prints the command synopsis and exits 0. There is no `--help` or `--version`; both are treated as unknown commands.

Commands that take optional file arguments share one selection rule. With no files named, the command discovers every `.hem` root covered by the nearest `tsconfig.json`. With files named, those files select their projects, and explicit inputs may span more than one nearest-tsconfig project. `inspect` requires one explicit module target.

A `.hem` file named on the command line, or reached through another file's import, must be covered by that tsconfig's `include` or `files`. A file outside the project is refused with a configuration diagnostic rather than checked in isolation.

## `hem explain`

```
hem explain <code>
hem explain --all
```

Prints the diagnostic registry's own title and explanation for a code.

`--all` prints every registered code sorted lexically. An unknown code exits 1 and lists the known identifiers. Arguments after the first are ignored.

## `hem inspect`

`hem inspect <file.hem[#export]> [--json]` checks the selected file's project and returns its exports, or one named export. Quote targets containing `#` when your shell requires it. It writes no generated files.

The report has `kind: "hem-inspect-report"` and `schemaVersion: 1`. `--json` writes one object to stdout with empty stderr; without it the same data is pretty-printed.

| Status | Meaning | Exit code |
| --- | --- | --- |
| `resolved` | A checked module or export is available | 0 |
| `unavailable` | The module or named export could not be resolved; see `reason` | 1 |
| `failed` | Compilation failed; `check` contains the check report and no provisional exports | 1 |
| `error` | Arguments, preparation, or inspection failed; see `reason` and `message` | 1 |

Resolved exports include checker-rendered types, all call signatures, documentation, and definition locations. Reexports follow their declarations. Locations distinguish `hem-authored` from `typescript-contract`; they include source hashes, one-based UTF-16 line/column, and half-open spans. Hem locations are position-map anchors, which may identify a token or containing construct. Foreign definitions use exact declaration spans. An export's static contract does not establish whether it exists at runtime.

Recognized Effect values and single-signature operations expose separate success, error, and service-requirement channels with union members. The Effect field explicitly reports `unavailable` for unrecognized values and overloads. Unavailable does not mean pure or requirement-free. These are static contracts, not runtime permissions or proof that a foreign implementation honors its declaration.

`toolchain` records Hem and TypeScript versions. `snapshotSha256` fingerprints checked Hem sources, the TypeScript program's source/declaration files, compiler options and Effect import setting. Definition hashes identify the exact source text behind locations. Re-inspect after changes; these hashes are freshness identifiers, not a portable build cache or runtime verification. Inspection uses checked in-memory output from Hem sources; it does not certify existing emitted siblings.

## `hem fmt`

```
hem fmt [--check] [files...]
hem fmt --stdin
```

Formats Hem source. Without options it rewrites changed files and prints their paths.

| Option | Effect |
| --- | --- |
| `--check` | Writes nothing; exits non-zero if any file needs formatting or fails to parse |
| `--stdin` | Reads source from stdin, writes formatted output to stdout |

`--stdin` cannot be combined with `--check` or with file arguments. Unknown options are rejected.

There is no `--print-width`. The CLI and the language server both use the formatter default.

## `hem check`

```
hem check [--json] [files...]
```

Runs the full project compilation and checking path without writing output. Reports HEM3008 when an existing generated sibling no longer matches the body hash in its banner, including stale siblings recorded in the manifest.

`--json` may appear before or after file arguments. Unknown options are rejected before project loading. For a filename starting with a dash, use a path such as `./-example.hem`; `--` is not an option terminator for this command.

### Machine-readable checks

`hem check --json` writes one JSON object to stdout and leaves stderr empty for reported check and project-loading failures. The report has `kind: "hem-check-report"` and `schemaVersion: 1`.

| Status | Meaning | Exit code |
| --- | --- | --- |
| `passed` | Checking finished without error diagnostics; hints and warnings may remain | 0 |
| `failed` | Checking finished with error diagnostics | 1 |
| `error` | Arguments, project selection, or preparation failed; checking did not finish | 1 |

`files` lists the checked source files on completed checks. On an operational error it records the selected or requested inputs, not proof that those files were checked; argument errors leave it empty. An `error` report also contains `error.kind` (`arguments`, `project-selection`, or `project-preparation`) and a human-readable `error.message`. Do not parse that message to identify a failure.

Each entry in `diagnostics` contains:

- `fileName`: the source or project context associated with the diagnostic.
- `diagnostic`: the existing structured diagnostic, including its stable `code`, `severity`, `message`, `span`, and any `notes`, `relatedInformation`, or `fix`.
- `authored`: the Hem file, one-based line and column, and half-open UTF-16 span. It is `null` for operational diagnostics without a precise source location; do not interpret a placeholder diagnostic span as a location in that case.
- `guidance`: a checker-backed contextual explanation when available, otherwise the registry explanation, or `null` when neither is available.

Safety diagnostics may also contain `diagnostic.evidence`. `write-transfer` identifies the conflicting property path and, for array widening, both element types. `invalidated-refinement` identifies the refused read and conservative invalidating operations with authored anchors; its status is `unavailable` when no mapped cause is available. The list is analysis provenance, not proof of a runtime mutation or an exhaustive execution trace. `effect-requirements` exposes checked service names for HEM4102 and supported missing-provider TS2345/TS2379 diagnostics. Evidence is additive: its absence on another diagnostic does not imply safety.

For an imported Effect operation passed to a configured Effect runner with
missing services, the JSON report names the checked requirements and explains
that they need Layers and `Effect.provide`. Terminal output from `hem check`
and a refused `hem build` includes the same guidance as a diagnostic note.
This guidance does not erase or
handle typed failures and does not supply an automatic edit. Lookalike APIs and
unrelated argument errors retain their ordinary explanations.

Quickfix edits use offsets into the original Hem source, not generated TypeScript. Apply edits only to the unchanged source that produced the report, from highest offset to lowest, then check again. A fix is not a guarantee that the whole program will pass. When no fix is supplied, use the diagnostic and guidance to choose a repair; do not add an unchecked assertion or move refused code into TypeScript to silence it.

For example, save this as `main.hem` in a configured Hem project:

```text
let mut count = 0
count++
export let answer = count
```

Run `hem check --json main.hem`. HEM1035 supplies a quickfix replacing `count++` with `count += 1`. Apply that edit to `main.hem`, then run the same check again. It passes; `hem build main.hem` produces a module exporting `answer` as `1`. The CLI regression suite also checks this repair with CRLF line endings and a preceding Unicode comment, then executes the emitted module.

This is a command-line repair loop, not evidence of language-server update handling or editor completion quality. Process crashes and startup failures are outside this report contract.

## `hem build`

```
hem build [--overwrite-edited] [files...]
hem build --watch [path]
```

Writes generated `.ts` and `.tsx` siblings, refuses collisions with handwritten files (HEM3002) or edits to generated siblings (HEM3008), removes unedited stale generated alternatives, and updates `.hem/generated-files.json`.

The banner carries `hem:sha256=<16 hex>`: the first 16 hex characters of SHA-256 over the body after the banner newline, with CRLF normalized to LF. An edited sibling is preserved, other files still build, and the command exits non-zero. The same guard protects stale-sibling removal. Move the change into the `.hem` source, then run `hem build --overwrite-edited` or delete the sibling and rebuild. Legacy banners without a hash remain overwritable and gain a hash on rebuild.

`--overwrite-edited` discards edits to generated siblings, regenerating them or removing edited stale siblings. Each overwrite or removal is reported on stderr; other errors still cause a non-zero exit. Handwritten-file collisions (HEM3002) remain refused. The flag is only available for one-shot builds: combining it with `--watch` is a usage error, and it is not exposed by the Vite plugin.

When two or more siblings are refused as edited, build, watch, and check add one note recommending formatter/linter exclusions via `hem generated exclusions` before bulk recovery. JSON check output includes the note once in diagnostic guidance.

In watch mode the first positional value is the directory to start project discovery from; further positional values are not treated as source files. Watch mode performs an initial build, then rebuilds on changes to `.hem` files, handwritten TypeScript/JavaScript files, package manifests, or tsconfig until interrupted with SIGINT or SIGTERM. Creating or deleting a sidecar also refreshes diagnostics. Inherited configs are watched by their resolved filenames, including configs outside the project directory and files whose names differ from `tsconfig.json`. Changing the inheritance chain updates those watchers; fixing an invalid inherited config triggers recovery. Other `node_modules` changes are ignored. Deleting a known sibling or changing a sibling that the last build refused as edited also triggers a rebuild, allowing deletion or restoration to recover immediately. Other sibling events are ignored. Vite uses the same watch and recovery behavior.

### Runtime debugging

Every nonempty generated sibling ends with an inline version-3 source map. Its source URL names the adjacent `.hem` file, escapes URL delimiters, and contains no absolute filesystem path or authored source content. The ownership hash covers the map as part of the generated body, so map edits receive the same HEM3008 protection as code edits. There is no extra `.map` file to clean up when a source is removed or switches between TS and TSX.

After `hem build`, Node 22.18 or later can run an erasable TypeScript sibling with `node --enable-source-maps src/task.ts`. Error-stack frames point to the authored Hem file. JSX still needs the project's JSX transform. Lowered control flow and generated scaffolding point to the nearest authored construct; this does not reproduce an authored variable environment or make every generated statement an independent Hem breakpoint.

The Vite plugin supplies the first map when loading the ordinary sibling. Vite's TypeScript/JSX transform and bundler compose it; enable `build.sourcemap` for production maps. Local development maps include the successfully compiled Hem source so DevTools displays the correct authored text, including after a failed rebuild. Production maps omit that content by default. `hem({ includeSourcesContent: true })` opts into publishing it; the downstream `sourcemapExcludeSources` option still wins. Publishing production maps is a deployment choice made through Vite's normal settings.

Plain `tsc` does not consume a TypeScript input map. For that pipeline, decode the sibling's final inline map using `parseRuntimeSourceMap` from `@hem-lang/compiler`, read the flat map emitted by `tsc`, and pass the two maps to `composeRuntimeSourceMaps(downstream, upstream, intermediateSource)`. The third argument is the exact generated TS entry in `downstream.sources`, such as `../src/task.ts` for an `outDir` build. Write the composed map where the JavaScript's `sourceMappingURL` points, then run JavaScript with `--enable-source-maps`. These helpers support flat version-3 maps with no `sourceRoot`; flatten index maps and normalize source roots with the downstream tool first. A map must be retained/composed at every transformation; tools that discard maps cannot provide authored runtime locations.

## `hem generated`

```
hem generated activate <project-directory>
hem generated verify <project-directory>
hem generated setup <project-directory...> [--relative-to <root>]
hem generated exclusions <project-directory...> [--relative-to <root>] [--format lines|json|biome]
```

| Subcommand | Behavior |
| --- | --- |
| `activate` | Verifies the manifest, then canonicalizes and rewrites it |
| `verify` | Checks the manifest and its source and sibling pairs, writing nothing |
| `setup` | Registers consumer projects and manages exact Git/Prettier ignore blocks; prints one-time ESLint, Vitest, and Biome wiring |
| `exclusions` | Prints the generated sibling paths for formatter, linter, and coverage configuration |

`--relative-to` rebases printed paths against a root, and is required when passing more than one project directory. A sibling that falls outside that root is an error.

`--format` defaults to `lines`. The alternatives are `json` and `biome`.

`setup` does not accept `--format`. It writes `.hem/tooling.json` at the consumer root and preserves existing text outside its hashed blocks in `.gitignore` and `.prettierignore`. Subsequent build, watch, and Vite updates commit the ownership manifest and enrolled ignore blocks together. Rerunning setup replaces the registered project set. It requires valid manifests and existing Hem sources; absent siblings are allowed, while existing siblings require current hashed ownership. It refuses edited blocks, ambiguous registrations, unreadable files, and symlink write destinations.

The `@hem-lang/cli/generated` exports `generatedESLintIgnores(root)` and `generatedExclusions(root, "eslint" | "coverage" | "biome")` read current registered manifests without evaluating configuration. Their patterns are based on the consumer root. Follow [the consumer setup guide](/guides/integrate-generated-files/) for the required configuration wiring and failure recovery.

Unknown options and invalid combinations print the usage text.

## `hem migrate`

```
hem migrate [--check] [--json] [--verbose] [--extract-implementation] <files.ts...>
```

Reads existing TypeScript. In the default apply mode it writes a `.hem` sibling for every file whose proposed migration both parses and type-checks as Hem, and refuses, leaving untouched, any file that does not. Every written file carries a banner marking it as migrated output.

| Option | Effect |
| --- | --- |
| `--check` | Prints a per-file, per-construct readiness report; writes nothing |
| `--json` | Emits the versioned machine-readable migration plan; requires `--check` |
| `--verbose` | Reports every refusal diagnostic instead of grouped first blockers |
| `--extract-implementation` | Keeps a TypeScript shell and moves self-contained function implementations into `.impl.hem` |

Inputs must be unique `.ts` or `.tsx` paths. Unknown options are rejected. `--json` without `--check` is an error, and `--extract-implementation` is incompatible with `--check`.

Semantic API redesigns, meaning the truthiness-driven surfaces a real port would rethink, are a stated non-goal of both modes.

## `hem test`

```
hem test [filters...] [--watch] [--effect <module>] [--jsx-import-source <module>] [--help]
```

For projects without a Vite or Vitest config, emits the project's siblings and runs Vitest with the Hem plugin wired in and the emitted TSX transform pinned to a JSX runtime. Selects `**/*.{test,spec}.{ts,tsx}` under the current directory.

If the current directory contains `vitest.config.*` or `vite.config.*` (extensions `ts`, `mts`, `cts`, `js`, `mjs`, or `cjs`), exits 1 before loading configuration, emitting files, or starting tests. Register `hem()` from `@hem-lang/vite-plugin` in that config and run the project's Vitest command to retain setup files, environment, coverage, and projects. Custom config paths also belong to the project's Vitest command; `hem test` does not discover or load them.

| Option | Effect | Default |
| --- | --- | --- |
| `--watch` | Leaves Vitest running and re-runs on change | Off |
| `--effect` | Overrides `hem.effectImportSource` | The tsconfig value |
| `--jsx-import-source` | Sets the JSX runtime for emitted TSX | `react` |
| `--help`, `-h` | Prints test-command usage without building or running tests | Off |

The two module options also accept the `--option=value` spelling. Values must contain a non-whitespace character and not begin with `-`. Module specifiers are preserved as supplied; quote relative paths containing spaces, for example `--effect './local transport.ts'`. Repeated module options use the last value. Positional values are passed to Vitest as test file path filters; use `--` before a filter beginning with `-`.

Unsupported options (including `--coverage`, `--config`, and `-t`) and missing or malformed module values exit 2 with usage before emitting or testing. Vitest options are not forwarded. Use the project's Vitest command for additional options, including test-name selection and coverage.

Vitest and `@hem-lang/vite-plugin` resolve from the project rather than from the CLI, so the project's own installed versions run. When either is missing, the command reports what to install and exits 1.

Failed tests, unhandled errors, and no matching test files fail the command. Passing assertions do not erase a Vitest runner failure.

## Normative source

Specification 0.2, §1 (compilation model), §13 (modules and interop), §16 (diagnostics).

Source: https://hem.run/reference/cli/index.mdx
