---
url: https://mikrojs.dev/cli.md
description: Complete reference for the mikro command-line tool
---

# CLI Reference

The `mikro` CLI is the main tool for building, deploying, and managing Mikro.js projects.

## mikro dev

Start development mode with live reload. Builds your TypeScript, deploys it to the device over serial, and watches for changes. Every time you save a file, the new code is sent to the device within seconds.

```sh
mikro dev [ENTRY]
```

| Option             | Description                                                                                             |
| ------------------ | ------------------------------------------------------------------------------------------------------- |
| `ENTRY`            | Entry file (default: `main` field in package.json)                                                      |
| `-p, --port PORT`  | Serial port (auto-detected if omitted)                                                                  |
| `--env-file FILE`  | Extra `.env` file, applied last (highest priority); see [precedence](/environment-variables#precedence) |
| `--no-auto-env`    | Skip auto-loading of `.env` and `.env.development`                                                      |
| `--force-deploy`   | Force full deploy, ignoring cached checksums                                                            |
| `--no-minify`      | Skip minification                                                                                       |
| `--no-bytecode`    | Skip bytecode compilation                                                                               |
| `--loglevel LEVEL` | Log level: `none`, `error`, `warn`, `info`, `debug`. Default: `debug`                                   |
| `--json`           | Output as JSON                                                                                          |

See [Build options](#build-options) for details on `--no-minify`, `--loglevel`, and other build flags.

If the app declares a [config schema](/ota#device-config), each deploy round ships the
manifest the config defaults live in, so `ota.config()` works without a registry. Per-device
values beyond the defaults come from a registry check-in.

To run on the host simulator instead of a device, use [`mikro sim dev`](#mikro-sim).

## mikro deploy

One-shot deploy to a connected device. This is treated as a production deployment: `MIKRO_ENV` is set to `"production"` and `--loglevel` defaults to `warn` (stripping `console.debug`, `console.log`, and `console.info` calls from the build).

The build is staged over the cable and installed at the next boot, before the app loads, so a deploy succeeds even when the running app has fragmented the device's heap. The deploy restarts the device, waits for it to come back, and reports the install outcome; on failure the previous app keeps running.

If the app declares a [config schema](/ota#device-config), the deployed build carries the
schema defaults in its manifest, and `ota.config()` reads them until a registry delivers a
document. The deploy also drops config-pairing state left over from a previous OTA install
cycle (a staged or rollback document, a running trial, the last failure report); the delivered document itself is
kept, and its version stamp decides whether the new build still reads it.

```sh
mikro deploy [ENTRY]
```

| Option             | Description                                                                                                      |
| ------------------ | ---------------------------------------------------------------------------------------------------------------- |
| `ENTRY`            | Entry file (default: `main` field in package.json)                                                               |
| `-p, --port PORT`  | Serial port (auto-detected if omitted)                                                                           |
| `--env-file FILE`  | Extra `.env` file, applied last (highest priority); see [precedence](/environment-variables#precedence)          |
| `--no-auto-env`    | Skip auto-loading of `.env` and `.env.production`                                                                |
| `--console`        | Attach console after deploy and restart device                                                                   |
| `--recover`        | Reset into safe mode before deploying. See [Troubleshooting](/troubleshooting#recovering-a-crash-looping-device) |
| `--no-restart`     | Stage the build without restarting; it installs at the next device boot                                          |
| `--no-minify`      | Skip minification                                                                                                |
| `--loglevel LEVEL` | Log level: `none`, `error`, `warn`, `info`, `debug`. Default: `warn`                                             |
| `--json`           | Output as JSON                                                                                                   |

See [Build options](#build-options) for details on `--no-minify`, `--loglevel`, and other build flags.

## mikro build

Build your app without deploying. Produces a build directory with bundled and optionally bytecode-compiled output.

```sh
mikro build [ENTRY] [-o DIR]
```

| Option              | Description                                                          |
| ------------------- | -------------------------------------------------------------------- |
| `ENTRY`             | Entry file (default: `main` field in package.json)                   |
| `-o, --out-dir DIR` | Output directory (default: `.mikro/build` in the project root)       |
| `--no-minify`       | Skip minification                                                    |
| `--no-bytecode`     | Skip bytecode compilation                                            |
| `--loglevel LEVEL`  | Log level: `none`, `error`, `warn`, `info`, `debug`. Default: `warn` |
| `--json`            | Output as JSON                                                       |

The output directory is deleted and recreated on every build. The default is inside the hidden `.mikro/` directory; to write somewhere you can browse, pass `-o`, for example `mikro build -o out`.

A directory passed with `-o` gets a `.mikro-build` marker file. The build deletes an existing directory only if it is empty or has this marker, so `mikro build -o .` or `-o src` fails instead of deleting your files.

See [Build options](#build-options) for details on `--no-minify`, `--loglevel`, and other build flags.

## mikro flash

Flash the Mikro.js runtime firmware to a device. You only need to do this once per board, or when updating Mikro.js.

```sh
mikro flash
```

| Option                | Description                                                                                                                                                                                                                                                                                                                                                                                                                         |
| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `-p, --port PORT`     | Serial port (auto-detected if omitted)                                                                                                                                                                                                                                                                                                                                                                                              |
| `--chip CHIP`         | The device's chip (for example `esp32c6`). Detected from the connected device if omitted                                                                                                                                                                                                                                                                                                                                            |
| `--board BOARD`       | Board name, for example `@acme/devboard` or `esp32c6-generic`. Without it: `board` in `mikro.config.ts`, else the board in the project's dependencies (the one for the device's chip when there are several), else the board a device on the generic firmware was flashed as, else the generic board for the chip                                                                                                                   |
| `--features FEATURES` | Flash the leanest of the board's [images](/develop/creating-boards#leaner-images) with these features, comma-separated (`wifi`, or `wifi,ble`), `no-<feature>` for the full image without that feature (`no-ble`, or `no-ble+no-wifi` as in the image names), `min` for the leanest image, or `full` for the full image. Without it, a reflash keeps the image the device runs, except with `--force`, which flashes the full image |
| `--build-dir DIR`     | Path to a local ESP-IDF build directory                                                                                                                                                                                                                                                                                                                                                                                             |
| `--from URL`          | The URL of a firmware archive (a `.tar.gz`, as [`mikro fw pack`](#mikro-fw-pack) writes it), flashed as it is                                                                                                                                                                                                                                                                                                                       |
| `--baud BAUD`         | Baud rate for flashing (default: `460800`)                                                                                                                                                                                                                                                                                                                                                                                          |
| `-y, --yes`           | Skip confirmation prompt                                                                                                                                                                                                                                                                                                                                                                                                            |
| `--force`             | Flash even if the device reports custom firmware, or the new partition table would shrink the app filesystem and erase its files. It doesn't read the device first, so it keeps neither the image the device runs nor the board a device on the generic firmware was flashed as                                                                                                                                                     |

The generic boards also have leaner images: `no-ble` without Bluetooth, and `no-ble+no-wifi` without either radio. An app that uses [`mikro/wifi`](/api/wifi) but not [`mikro/ble`](/api/ble) gets more memory with `mikro flash --features wifi`, which flashes `no-ble`: the leanest image with WiFi. An app that uses neither gets `no-ble+no-wifi` with `mikro flash --features min`, the leanest image. The ESP32 also has `no-wifi`, for an app that uses BLE but not WiFi: `mikro flash --features ble` flashes it. When an app needs a feature the device's image lacks, `mikro dev`, `mikro deploy` and `mikro test` stop and print the `--features` to flash with.

On a module with more flash than the firmware was built for, the generic firmware, a board package's image and `--from` firmware give the [app filesystem](/developing-for-microcontrollers#filesystem) the rest of the flash, up to 16 MB. This applies when `user` is the last partition, as in the default partition table.

A reflash of the generic firmware or a board package's image writes only what changed. It skips the bootloader and partition table when the device already has them. When the device runs the same image at the same version, it writes only the 4 KB sectors of the app that differ: a new board name changes two. The flash is checked first each time, so a device that holds something else gets the whole file. `--force` doesn't read the device and writes the whole app, and `--build-dir` and `--from` write every file.

::: tip
`--build-dir` and `--from` are mutually exclusive. Use `--build-dir` for firmware you built from source, and `--from` for an archive someone shared. To flash the firmware of another Mikro.js version, install that version of `mikro` and run `pn mikro flash`.
:::

## mikro idf

Run ESP-IDF's `idf.py` to build and flash [custom firmware](/develop/custom-firmware). The arguments go to `idf.py` unchanged:

```sh
pn mikro idf set-target esp32c6
pn mikro idf build flash monitor
```

When `idf.py` is not on `PATH`, the command runs it through [EIM](https://docs.espressif.com/projects/idf-im-ui/en/latest/) with `eim run`, which activates ESP-IDF first. It exits with `idf.py`'s exit code.

In a firmware project (a folder with a `CMakeLists.txt`), it also tells CMake where the project's `@mikrojs/firmware` is: it resolves the package from the project and passes `-DMikroFirmware_DIR` to `idf.py`, for the project's `find_package(MikroFirmware ...)`. A firmware project does not build with plain `idf.py`, so use `mikro idf` for every step, including `flash` and `monitor`.

## mikro fw

### mikro fw pack

Build a [custom firmware](/develop/custom-firmware) project and pack it into an archive that [`mikro flash --from`](#mikro-flash) can flash: `flasher_args.json`, the files it lists, and `firmware.json`. Run it in the project folder. In a [board package](/develop/creating-boards), it runs `mikro fw build` first and packs each board's image. A board that [runs the generic firmware](/develop/creating-boards#boards-on-the-generic-firmware) gets its `firmware.json` and no archive: its image ships with `mikro`.

```sh
pn mikro fw pack
```

| Option          | Description                                                                                                                                                                                                     |
| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--out FILE`    | Output path for the archive (default: `./mikro-fw-<name>-<chip>.tar.gz`, without the chip when the name already ends with it or is `<chip>-generic`, or `./mikro-fw-<chip>.tar.gz` for firmware without a name) |
| `--board BOARD` | In a board package, pack only this board: its key in `boards.config.ts` (`./t-display`, or `t-display`) or its name. A bare `--board` asks which, or without a terminal lists the boards                        |
| `--parallel N`  | In a board package, build up to N images at once, as [`mikro fw build --parallel`](#mikro-fw-build) does                                                                                                        |

### mikro fw build

Build the boards in a [board package](/develop/creating-boards)'s `boards.config.ts` and write each image into the folder that the board's `firmware` export points at, then check the package as `mikro fw check` does. Run it in the package. Each board builds from a firmware project generated in `.mikro/fw-<board>`, into `.mikro/build-fw-<board>` (`.mikro/fw` and `.mikro/build-fw` for the board at `.`). A board package runs it from npm's `prepack` script, so `npm pack` and `npm publish` always include fresh images. Each board's new images replace its image folder once they are all built, so a failed build leaves the last ones. Before it builds, it stops if the folder holds anything but that board's images, so a `dist` set to the wrong folder loses nothing. A board that [runs the generic firmware](/develop/creating-boards#boards-on-the-generic-firmware) needs no ESP-IDF: `mikro fw build` writes its `firmware.json`, which names the generic board.

```sh
pn mikro fw build
```

| Option          | Description                                                                                                                                                                                                                                                                                           |
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--board BOARD` | Build only this board: its key in `boards.config.ts` (`./t-display`, or `t-display`) or its name. A bare `--board` asks which, or without a terminal lists the boards                                                                                                                                 |
| `--image IMAGE` | Build only this image of each board, `full` or one of its [leaner images](/develop/creating-boards#leaner-images), and keep the others. A bare `--image` asks which, or without a terminal lists the images. A board that runs the generic firmware has no images of its own, so `--image` refuses it |
| `--parallel N`  | Build up to N images at once, from all the boards, instead of one after another (`--parallel 4`). Each build writes its output to a log beside its build folder (`.mikro/build-fw+no-ble.log`), and a line per image says how it went. After a failure, no more builds start                          |
| `--flash`       | Then flash the image it built, as [`mikro flash`](#mikro-flash) does. It needs one board (`--board` in a package with several) and, for a board with leaner images, `--image`                                                                                                                         |

### mikro fw list

List the boards in a [board package](/develop/creating-boards)'s `boards.config.ts` and their images, one line per board. A board that [runs the generic firmware](/develop/creating-boards#boards-on-the-generic-firmware) shows the generic board instead (`runs esp32-generic`). With `--json`, it prints them as JSON, for example to build each image in a CI job of its own with `mikro fw build --board <board> --image <image>`. There a generic board has `firmware` and no images, so a matrix built from `images` has no job for it; a plain `mikro fw build`, such as the one in `prepack`, writes its `firmware.json`.

```sh
pn mikro fw list
```

### mikro fw check

Check a board package's `boards.config.ts`, its `firmware` exports and their images: that the exports match the config, and that each image is built, complete, named correctly, from a Mikro.js version the CLI accepts, not older than its last build, and included in `files`. It exits with an error if anything is wrong.

```sh
pn mikro fw check
```

## mikro ls

List connected devices. Each line shows the device's name, its serial port path, and details:

```sh
mikro ls
```

```
swift-otter  /dev/tty.usbmodem1101  (chip=esp32c6, id=2m68224yym)
```

The name is the [name](#mikro-name) the device reports for itself if one is set, otherwise its stable device ID. `id` is that same device ID as the firmware reports it; `chip` appears once you've connected to the device at least once.

| Option   | Description                                                                                          |
| -------- | ---------------------------------------------------------------------------------------------------- |
| `--json` | Output as JSON (includes `name`, `deviceName`, `deviceId`, `serialNumber`, `chip`, and USB metadata) |

## mikro name

Give a device a memorable name. The device stores it itself, so it travels with the board: plug it into another machine and it still answers to the same name, and a registry the device is enrolled with shows the same one.

```sh
mikro name              # show the connected device's name
mikro name set [NAME]   # name it (omit NAME to use an `adjective-animal` suggestion, like `swift-otter`)
mikro name unset        # clear the name
```

| Option            | Description                                                                      |
| ----------------- | -------------------------------------------------------------------------------- |
| `-p, --port PORT` | Device to act on (path, serial, or name). Defaults to the only connected device. |
| `--json`          | Output as JSON                                                                   |

Because the name is written to the device, these need it connected. You can also run `/name set <name>` / `/name unset` inside `mikro console` or `mikro dev`.

A device with no name set shows its device ID instead. The suggestion offered by `mikro name set` is only ever used to create a name you then keep — nothing displays it until it is stored, so upgrading the CLI can never rename a board behind your back.

Devices enrolled with an update registry are always named: `mikro ota enroll` writes one if you don't pass `--name`, and thereafter a rename on either side syncs to the other at the next check-in.

Anywhere a command takes `--port`, the value can be a device path, its name, serial number, or device ID.

## mikro console

Connect to the device's serial console. Shows runtime output and provides a REPL for interactive evaluation.

```sh
mikro console
```

| Option            | Description                                                                                                                                        |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `-p, --port PORT` | Serial port (auto-detected if omitted)                                                                                                             |
| `--raw`           | Plain serial passthrough (no protocol framing)                                                                                                     |
| `--hex`           | Show raw hex bytes                                                                                                                                 |
| `--recover`       | Reset into safe mode before connecting (inspect a crash-looping device). See [Troubleshooting](/troubleshooting#recovering-a-crash-looping-device) |

## mikro logs

Read or follow device logs. Three subcommands:

* `mikro logs tail` — live stream from the wire (no `logFile` required).
* `mikro logs pull` — read the on-device file written by the file logger (requires [`logFile`](/config#logfile) in `mikro.config.ts`).
* `mikro logs reset` — clear the on-device log files (requires [`logFile`](/config#logfile) in `mikro.config.ts`).

### mikro logs tail

Stream console output from the device without an interactive REPL. Useful for monitoring a running app or piping output to other tools.

```sh
mikro logs tail
```

| Option            | Description                                                                                     |
| ----------------- | ----------------------------------------------------------------------------------------------- |
| `-p, --port PORT` | Serial port (auto-detected if omitted)                                                          |
| `-r, --restart`   | Restart the device first                                                                        |
| `--loglevel`      | Drop device output below this level: `none`, `error`, `warn`, `info`, `debug` (default `debug`) |

### mikro logs pull

Pull the on-device log file written by the file logger. With no destination, the log is streamed to stdout — older rotated content first, then the current generation, so the output is chronological. With a destination directory, both `log.txt` and `log.txt.1` (if present) are written there as separate files.

```sh
# Stream to stdout
mikro logs pull

# Pipe through tools
mikro logs pull | grep ERROR

# Archive both generations
mikro logs pull ./forensics
```

| Option            | Description                            |
| ----------------- | -------------------------------------- |
| `DEST`            | Optional directory to write files into |
| `-p, --port PORT` | Serial port (auto-detected if omitted) |

The file logger flushes and releases its handle while the file is streamed off, so the pull sees a consistent snapshot. Any log lines emitted during the transfer are dropped; in practice this is a sub-second window.

### mikro logs reset

Clear the on-device log files. The device deletes both `log.txt` and `log.txt.1` and reopens a fresh, empty log. The running app is not interrupted and the device is not restarted.

```sh
mikro logs reset
```

| Option            | Description                            |
| ----------------- | -------------------------------------- |
| `-p, --port PORT` | Serial port (auto-detected if omitted) |

## mikro test

Run on-device tests. Discovers `*.test.ts` files, deploys them, and reports structured results. Each file runs in a fresh runtime with a full heap, so tests are isolated from each other.

```sh
mikro test [PATTERN]
```

| Option                  | Description                                                                                                                        |
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `PATTERN`               | Glob pattern to filter test files (default: `**/*.test.ts`)                                                                        |
| `-p, --port PORT`       | Serial port (auto-detected if omitted)                                                                                             |
| `--env-file FILE`       | Extra `.env` file, applied last (highest priority); see [precedence](/environment-variables#precedence)                            |
| `--no-auto-env`         | Skip auto-loading of `.env` and `.env.test`                                                                                        |
| `--no-minify`           | Skip minification                                                                                                                  |
| `--no-bytecode`         | Skip bytecode compilation                                                                                                          |
| `-t, --timeout MS`      | Per-file timeout in ms (default: `60000`)                                                                                          |
| `-u, --update-heap`     | Overwrite committed heap snapshots with this run's measurements, boot figures included                                             |
| `--heap-tolerance SIZE` | Heap drift below which a snapshot is neither flagged nor rewritten                                                                 |
| `--diagnostics`         | Show per-test heap progress and supervisor announcements                                                                           |
| `--isolate`             | Restart the device before every file so each starts from a fresh boot; slower, but run order cannot affect results or heap figures |
| `-y, --yes`             | Skip confirmation prompt                                                                                                           |
| `--json`                | Output as JSON                                                                                                                     |

See [Build options](#build-options) for details on `--no-minify` and other build flags.

```sh
# Run all tests
mikro test

# Run a specific test file
mikro test 'test/smoke.test.ts'

# Run with env vars
mikro test --env-file .env
```

Test files import from `mikro/test`:

```ts twoslash
import {describe, test, assert} from 'mikro/test'

describe('my feature', () => {
  test('works', () => {
    assert.equal(1 + 1, 2)
  })
})
```

The environment variable `MIKRO_ENV` is automatically set to `"test"` during test runs.

::: tip
`mikro test` overwrites whatever app is currently on the device with the test manifest. The confirmation prompt exists so you don't do that by accident. Pass `-y` in CI or scripted runs when you know what you're doing.
:::

To run tests in the host simulator, use [`mikro sim test`](#mikro-sim).

## mikro profile

Report the memory a device leaves for an app to consume. This is the device counterpart to the `QuickJS baseline` line in [`mikro sim profile`](#mikro-sim-profile): what is available before any of your code costs anything.

```sh
mikro profile
```

```
  esp32c6  js 218KB   system 252.4KB
```

There are two ceilings, and either one can be the first you hit:

* **js**: the JS budget left before `mem_limit` throws `InternalError: out of memory`. This is the figure the firmware boot banner prints.
* **system**: the free chip heap that native allocations draw on (TLS records, WiFi buffers, drivers).

QuickJS allocates out of the system heap, so JS growth is capped by whichever is smaller. Both are stored as `boot` in `__heap_snapshots__/<chip>.json`, the same file [`mikro test`](#mikro-test) writes per-test figures to. A test run reads the same handshake and records them, so the usual way to keep the figures current is to run the suite. This command reports them on demand and writes only with `--write`.

They track the firmware and the project's runtime configuration rather than app code, so they move when sdkconfig, native modules, or the ESP-IDF version move. They also move when [`memReserved`](/config#memreserved) changes, since `mem_limit` is derived as free heap minus that reserve. `memReserved` moves **js** one-for-one and leaves **system** untouched, which is what tells a reserve change apart from a real firmware regression.

The device reports both in the ready handshake, captured at boot before it evaluated your app, so a normal run only connects and reads. There is one exception: when the device booted with a different `memReserved` than the project config, the reading would describe the old reserve, so the command offers to deploy the project first. Accepting replaces the app on the device with a production build.

| Option                  | Description                                                     |
| ----------------------- | --------------------------------------------------------------- |
| `-p, --port PORT`       | Serial port (auto-detected if omitted)                          |
| `--write`               | Record this run's reading as the committed boot snapshot        |
| `--heap-tolerance SIZE` | Drift below which the snapshot is neither flagged nor rewritten |
| `--json`                | Output as JSON                                                  |

When a reading shows less free memory than the stored figure, the command reports a regression and exits non-zero; more free memory is reported as an improvement worth recording. A move in either ceiling counts, and `--write` records both.

::: tip
Firmware predating this handshake field does not report the figures, and the command says so. Rebuild and flash to get them.
:::

## mikro env

Manage environment variables stored on the device. Variables persist across reboots and are accessible via [`mikro/env`](/api/env) or `import.meta.env` in your code.

### mikro env list

List all environment variables on the device.

```sh
mikro env list
```

| Option            | Description                            |
| ----------------- | -------------------------------------- |
| `-p, --port PORT` | Serial port (auto-detected if omitted) |
| `--json`          | Output as JSON                         |

### mikro env set

Set an environment variable on the device.

```sh
mikro env set KEY [VALUE]
```

| Option            | Description                                                                                                 |
| ----------------- | ----------------------------------------------------------------------------------------------------------- |
| `KEY`             | Variable name (max 15 characters)                                                                           |
| `VALUE`           | Variable value. Allowed only with `--no-secret`. Without it, omit `VALUE` and the CLI prompts you for it    |
| `--no-secret`     | Pass `VALUE` as an argument and store it as non-secret (visible in `env list`). Requires a `VALUE` argument |
| `-p, --port PORT` | Serial port (auto-detected if omitted)                                                                      |

Secrets are entered via a hidden prompt so they don't leak into shell history or `ps` output. Plain values can be passed inline with `--no-secret`.

```sh
# Secret: prompts for value (hidden input)
mikro env set API_KEY

# Non-secret: pass VALUE inline
mikro env set WIFI_SSID MyNetwork --no-secret
```

You can also mark `.env` entries non-secret with a `# @no-secret` comment line above them.

### mikro env delete

Delete an environment variable from the device.

```sh
mikro env delete KEY
```

| Option            | Description                            |
| ----------------- | -------------------------------------- |
| `KEY`             | Variable name to delete                |
| `-p, --port PORT` | Serial port (auto-detected if omitted) |

## mikro ota

Pack, push, and release app builds for over-the-air updates, and enroll devices with an update registry. See the [OTA guide](/ota).

Which registry to use lives in `.mikro/registry.json` (project) or `~/.mikro/registry.json` (user), as `{"url": …, "token": …}`; `push`, `release`, and `enroll` read it so none need flags once it is set. Precedence: flags, then `MIKRO_OTA_TOKEN`, then the project file, then the user file.

### mikro ota setup

Configure which update registry to use and write it to `.mikro/registry.json`. Prompts for the registry url (checked for reachability), then obtains the token: registries that support browser login print a one-time code and offer to open the approval page (press Enter, or open the url yourself), then wait for the exchange. The CLI sends your project's app name so the registry can offer a token scoped to it. Other registries fall back to a hidden token prompt. Re-run it any time the registry moves or the token rotates; existing values are offered as defaults.

The browser is only opened from an interactive local terminal, since someone has to press Enter. Over ssh, in agent mode, or with stdin redirected, setup prints the url and the code and waits for you to approve from wherever your browser is.

```sh
mikro ota setup
```

| Option           | Description                                                            |
| ---------------- | ---------------------------------------------------------------------- |
| `--registry URL` | Registry base URL (skips the url prompt)                               |
| `--token TOKEN`  | Registry token (with `--registry`, setup runs without prompts)         |
| `--user`         | Write `~/.mikro/registry.json` (all projects) instead of the project's |
| `--force`        | Accept a url that does not identify itself as a Mikro.js registry      |

### mikro ota pack

Build the current project to bytecode and pack it into a deployable OTA build: a `.tgz` with a manifest recording the app name, the firmware version it requires, and the bytecode version it targets. An app that declares a [config schema](/ota#device-config) gets it recorded there too, alongside the defaults it materializes: the registry validates config against the schema, and the device reads the defaults.

```sh
mikro ota pack
```

| Option             | Description                                                            |
| ------------------ | ---------------------------------------------------------------------- |
| `--out FILE`       | Output path for the build (default: `./app-<version>.tgz`)             |
| `--snapshot`       | Derive a unique version so iteration needs no version bump (see below) |
| `--no-minify`      | Skip minification                                                      |
| `--loglevel LEVEL` | Log level: `none`, `error`, `warn`, `info`, `debug`                    |

See [Build options](#build-options) for details on `--no-minify`, `--minifier`, and other build flags.

### mikro ota push

Build, pack, and upload a build to your OTA registry. Without `--tarball`, the current project is built and packed first (same as `mikro ota pack`). By default the build is uploaded but not served to any device; pass `--release CHANNEL` to also point a channel at it (see [Release channels](/ota#release-channels)).

```sh
mikro ota push
```

| Option              | Description                                                            |
| ------------------- | ---------------------------------------------------------------------- |
| `--registry URL`    | Registry origin (default: `.mikro/registry.json`)                      |
| `--tarball FILE`    | Push a pre-packed `.tgz` instead of building                           |
| `--release CHANNEL` | Also release the build to this channel (default: not served)           |
| `--token TOKEN`     | Registry auth token (default: `MIKRO_OTA_TOKEN` env var)               |
| `--note TEXT`       | Free-text note stored with the build (for example what changed)        |
| `--create`          | Create the app on first push instead of failing on an unknown app      |
| `--snapshot`        | Derive a unique version so iteration needs no version bump (see below) |

See [Build options](#build-options) for details on `--no-minify`, `--loglevel`, and other build flags.

A registry stores each `(app, version, firmware range)` build once, so re-pushing changed bytes without bumping `package.json` fails (re-pushing the same bytes is an idempotent success, so a CI retry is safe). `--snapshot` sidesteps that during development by appending the UTC build time to the version as a semver prerelease (`1.2.3-snapshot.20260723T003205Z`). Snapshot versions therefore sort in build order under semver, so listed or sorted versions read chronologically; which build a device installs is decided by checksum, not by version order. Every push creates a new version, including a re-push of the same commit; which commit a build came from is recorded separately, in the build's source fields. With `--tarball`, pack with `--snapshot` instead; the version is fixed once a build is packed.

### mikro ota release

Point a channel at a build that is already on the registry. Uploading (`push`) and releasing are separate steps: `release` moves the channel pointer only, it uploads nothing. Use it to graduate a proven build to a wider channel (`mikro ota release 1.2.3 main`) or to roll a channel back to an earlier version. See [Release channels](/ota#release-channels).

```sh
mikro ota release <version> <channel>
```

| Option           | Description                                                   |
| ---------------- | ------------------------------------------------------------- |
| `VERSION`        | Version of an already-published build to serve                |
| `CHANNEL`        | Channel to point at that build (for example `beta` or `main`) |
| `--registry URL` | Registry origin (default: `.mikro/registry.json`)             |
| `--token TOKEN`  | Registry auth token (default: `MIKRO_OTA_TOKEN` env var)      |

### mikro ota enroll

Enroll the connected device with an update registry: reads the device's hardware id, registers it, and writes the registry url and the returned update key to the device's system store, where deploys never touch them. See [Enrolling devices](/ota#enrolling-devices).

```sh
mikro ota enroll
```

| Option                | Description                                                                           |
| --------------------- | ------------------------------------------------------------------------------------- |
| `--registry URL`      | Registry origin (default: `.mikro/registry.json`)                                     |
| `--token TOKEN`       | Registry API token (default: `MIKRO_OTA_TOKEN` env var)                               |
| `--name NAME`         | Display name stored with the device in the registry                                   |
| `--channel CHANNEL`   | Release channel to enroll the device on (default: `main`)                             |
| `--re-enroll`         | Rotate the update key when the device is already enrolled (the old one stops working) |
| `--update-key SECRET` | Write an externally issued update key to the device; the registry is not contacted    |
| `-p, --port PORT`     | Serial port (auto-detected if omitted)                                                |
| `--json`              | Output as JSON                                                                        |

## mikro clean

Remove the deployed app from the device. The device restarts and boots into the REPL with no app.

```sh
mikro clean
```

| Option            | Description                                                                                                     |
| ----------------- | --------------------------------------------------------------------------------------------------------------- |
| `-p, --port PORT` | Serial port (auto-detected if omitted)                                                                          |
| `--full`          | Remove all files and environment variables, not just the deployed app                                           |
| `--recover`       | Reset into safe mode before cleaning. See [Troubleshooting](/troubleshooting#recovering-a-crash-looping-device) |
| `-y, --yes`       | Skip confirmation prompt                                                                                        |

To clean simulator state, use [`mikro sim clean`](#mikro-sim) or [`mikro sim reset`](#mikro-sim).

With `--full`, all user-created files and environment variables are also removed. This requires confirmation (skip with `-y`).

## mikro erase

Erase all flash on the device, performing a full factory reset. This removes firmware, application code, and all stored data.

```sh
mikro erase
```

| Option            | Description                            |
| ----------------- | -------------------------------------- |
| `-p, --port PORT` | Serial port (auto-detected if omitted) |
| `--baud BAUD`     | Baud rate (default: `460800`)          |
| `-y, --yes`       | Skip confirmation prompt               |

::: danger
This erases everything on the device. You will need to re-flash firmware and re-deploy your app afterwards.
:::

## mikro sim

Run your code in the host simulator instead of on a real device. The simulator boots the same QuickJS runtime as a board would, talks the same protocol, and persists files and environment variables under `.mikro/sim-fs/` and `.mikro/nvs.json`.

Only one sim process can run at a time per project. Long-lived commands (`sim dev`, `sim repl`) kill any predecessor on start. One-shot commands (`sim deploy`, `sim test`, `sim env`, `sim profile`, `sim clean`, `sim reset`) refuse to run while a sim is already alive.

Hardware builtins are stubbed; see [Host simulator](/developing-for-microcontrollers#host-simulator) for details. Override the stubs by writing your own `sim/<builtin>.stub.ts` files (use `mikro sim scaffold` to generate starting points). Simulator memory and filesystem limits are configured in [`mikro.config.ts`](/config#sim).

### mikro sim dev

Watch + build + deploy + REPL against the simulator. The sim equivalent of `mikro dev`.

```sh
mikro sim dev [ENTRY]
```

| Option            | Description                                                                                             |
| ----------------- | ------------------------------------------------------------------------------------------------------- |
| `ENTRY`           | Entry file (default: `main` field in package.json)                                                      |
| `--env-file FILE` | Extra `.env` file, applied last (highest priority); see [precedence](/environment-variables#precedence) |
| `--no-auto-env`   | Skip auto-loading of `.env` and `.env.simulator`                                                        |
| `--no-minify`     | Skip minification                                                                                       |
| `--no-bytecode`   | Skip bytecode compilation                                                                               |

### mikro sim deploy

One-shot build + deploy to the simulator (no watch). The sim equivalent of `mikro deploy`.

```sh
mikro sim deploy [ENTRY]
```

| Option            | Description                                                                                             |
| ----------------- | ------------------------------------------------------------------------------------------------------- |
| `ENTRY`           | Entry file (default: `main` field in package.json)                                                      |
| `--env-file FILE` | Extra `.env` file, applied last (highest priority); see [precedence](/environment-variables#precedence) |
| `--no-auto-env`   | Skip auto-loading of `.env` and `.env.simulator`                                                        |
| `-e, --erase`     | Erase current app before uploading                                                                      |
| `--no-restart`    | Do not restart sim after deploy                                                                         |
| `--no-minify`     | Skip minification                                                                                       |
| `--no-bytecode`   | Skip bytecode compilation                                                                               |
| `--json`          | Output as JSON                                                                                          |

### mikro sim repl

Open an interactive REPL session on a fresh sim process. The sim equivalent of `mikro console`.

```sh
mikro sim repl
```

### mikro sim test

Discover and run `*.test.ts` files in the simulator. The sim equivalent of `mikro test`.

```sh
mikro sim test [PATTERN]
```

| Option                  | Description                                                                                             |
| ----------------------- | ------------------------------------------------------------------------------------------------------- |
| `PATTERN`               | Glob pattern to filter test files (default: `**/*.test.ts`)                                             |
| `--env-file FILE`       | Extra `.env` file, applied last (highest priority); see [precedence](/environment-variables#precedence) |
| `--no-auto-env`         | Skip auto-loading of `.env` and `.env.simulator`                                                        |
| `--no-minify`           | Skip minification                                                                                       |
| `--no-bytecode`         | Skip bytecode compilation                                                                               |
| `-t, --timeout MS`      | Per-file timeout in ms (default: `60000`)                                                               |
| `-u, --update-heap`     | Overwrite committed heap snapshots with this run's measurements                                         |
| `--heap-tolerance SIZE` | Heap drift below which a snapshot is neither flagged nor rewritten                                      |
| `--diagnostics`         | Show per-test heap progress and supervisor announcements                                                |
| `--json`                | Output as JSON                                                                                          |

### mikro sim env

Manage simulator environment variables. The sim equivalent of `mikro env`.

```sh
mikro sim env list
mikro sim env get KEY
mikro sim env set KEY [VALUE] [--no-secret]
mikro sim env delete KEY
```

### mikro sim clean

Remove the deployed app from the simulator (`sim-fs/app/`), leaving environment variables intact.

```sh
mikro sim clean
```

### mikro sim reset

Erase the entire simulator state: filesystem and environment variables.

```sh
mikro sim reset [-y]
```

| Option      | Description              |
| ----------- | ------------------------ |
| `-y, --yes` | Skip confirmation prompt |

### mikro sim profile

Run your app in the simulator and report per-module QuickJS heap usage, so you can see which imports are fat and which are cheap before deploying. Useful for debugging `InternalError: out of memory` failures where the cold-start module load is the suspect.

```sh
mikro sim profile [ENTRY]
```

| Option               | Description                                                                                             |
| -------------------- | ------------------------------------------------------------------------------------------------------- |
| `ENTRY`              | Entry file (default: `main` field in package.json)                                                      |
| `--mem-limit BYTES`  | QuickJS heap ceiling for the profile run (default: `32M`)                                               |
| `--memory-budget KB` | Highlight rows against an explicit budget in KB                                                         |
| `--chip NAME`        | Preset budget for a chip (for example `esp32c6` or `esp32s3`)                                           |
| `--top N`            | Show only the N largest modules                                                                         |
| `--sort KEY`         | Sort by `size` (default) or `order` (load order)                                                        |
| `--min-bytes N`      | Hide modules smaller than N bytes                                                                       |
| `--include-native`   | Include `native:*` runtime modules (excluded by default)                                                |
| `--include-builtins` | Include `mikro/*` built-in modules (excluded by default)                                                |
| `--only-native`      | Show only `native:*` modules                                                                            |
| `--only-builtins`    | Show only `mikro/*` built-ins                                                                           |
| `--json`             | Output as JSON                                                                                          |
| `--env-file FILE`    | Extra `.env` file, applied last (highest priority); see [precedence](/environment-variables#precedence) |
| `--no-auto-env`      | Skip auto-loading of `.env` and `.env.development`                                                      |

By default, `native:*` runtime modules and `mikro/*` built-ins are hidden so you see only your own code's heap cost. Use `--include-native` / `--include-builtins` to add them back, or `--only-native` / `--only-builtins` for a focused view of just those categories.

The `--mem-limit` is intentionally high (default `32M`) so the measurement run doesn't OOM. The profile output shows whether the app fits in the actual device budget.

### mikro sim scaffold

Generate starting `sim/<builtin>.stub.ts` files in the project's `sim/` directory. Use `--overwrite` to replace existing stubs.

```sh
mikro sim scaffold [--overwrite]
```

## Build options {#build-options}

Commands that build your code (`dev`, `deploy`, `build`, `test`, `ota pack`, `ota push`, and the `sim` equivalents) share these options:

* `--no-minify` disables minification. Useful for debugging; the output is larger but more readable in stack traces.
* `--minifier MINIFIER` selects the minifier: `esbuild` (default), `terser`, or `swc`. Can also be set via [`build.minifier`](/config#buildminifier).
* `--minify-level LEVEL` sets minification aggressiveness: `default` or `max`. Can also be set via [`build.minifyLevel`](/config#buildminifylevel).
* `--no-bytecode` skips compiling JavaScript to QuickJS bytecode. The device will parse JavaScript source at runtime, which uses more memory and is slower to start.
* `--loglevel LEVEL` sets the build-time log level. Console calls below the threshold are eliminated as dead code by the minifier. Levels from most to least verbose: `debug` > `info` > `warn` > `error` > `none`. `deploy` and `build` default to `warn` and `dev` to `debug`, unless [`build.logLevel`](/config#buildloglevel) is set.

## Shell completion

`mikro` ships completion scripts for bash, zsh, and fish. Once installed, pressing Tab completes subcommands, flags, file paths, and, for `-p` / `--port`, the serial devices currently plugged into your machine.

Generate and install the script for your shell:

::: code-group

```sh [bash]
mkdir -p ~/.bash_completion.d
mikro completion bash > ~/.bash_completion.d/mikro
echo 'source ~/.bash_completion.d/mikro' >> ~/.bashrc
```

```sh [zsh]
mikro completion zsh > "${fpath[1]}/_mikro"
# reload completions in the current shell:
autoload -U compinit && compinit
```

```sh [fish]
mikro completion fish > ~/.config/fish/completions/mikro.fish
```

:::

Then start a fresh shell session and try:

```sh
mikro <TAB>              # → dev, deploy, env, build, flash, ...
mikro dev --<TAB>        # → --port, --env-file, --no-minify, ...
mikro dev --port <TAB>   # → live serial devices on your machine
```

Completion is also available as the `--completion <shell>` option, useful for piping into a one-off file without invoking the subcommand form.

### Project-local installs

Shell completion only fires when the shell parses `mikro` as the first token of the command line. So it works for global installs and direnv-style setups that put `node_modules/.bin` on `$PATH`, but not for `pnpm mikro`, `npx mikro`, or `npm run` wrappers.

If you prefer a local install, a small shell function makes completion work by routing the bare `mikro` name through `PATH`, with the project's `node_modules/.bin` prepended:

```sh
mikro() {
  PATH="./node_modules/.bin:$PATH" command mikro "$@"
}
```

Add it to your `.bashrc` / `.zshrc`. The local binary wins when present; otherwise it falls back to a global install. Note that prepending a relative path has the same trust implication as `direnv`'s `PATH_add`: a malicious project could plant its own `node_modules/.bin/mikro`.
