Skip to main content
Docs navigation

Runner CLI

The emisar binary runs the daemon that executes gated actions. It is also the local toolbox for packs, journals, audit verification, and signed dispatch. This page lists each operator command, its key flags, and whether it changes the host.

Commands, flags, config keys, and environment variables are v1 compatibility surfaces. See Compatibility and deprecation .

web-01
$ sudo emisar status          # live daemon + connection summary
$ sudo emisar doctor          # offline preflight when status reports a problem
$ sudo emisar events tail -f  # the host-side security log, live
$ sudo emisar pack list
ID            VERSION  ACTIONS  HASH                 DESCRIPTION
debugging     0.2.10   31       sha256:ae3b9a7c8f11  General-purpose Linux diagnostics + remediation…
linux-core    0.3.19   34       sha256:8d0ba17ca0e4  Read-only Linux diagnostics + narrow service control…
systemd-deep  0.1.10   24       sha256:84f814476179  Deeper systemd state + operator surface…

Running the CLI#

On an installed host the binary lives at /usr/local/bin/emisar and every subcommand auto-discovers its configuration, so you rarely pass --config. The config, cached token, and packs directory are root-owned, so run commands with sudo. Running emisar with no arguments prints a Paths footer showing exactly where this host keeps its config, packs, token, and journal.

  • --config <path> — the config.yaml to load. Defaults to $EMISAR_CONFIG, then the /etc/emisar/config.yaml install location, then a per-user ~/.config/emisar/config.yaml.
  • --packs-dir <dir> — extra pack search directories, overriding the config's paths.packs (repeatable). Lets a read command work without a full config.
  • --json — machine-readable JSON, on action list, every pack verb except install and uninstall, status, doctor, audit verify, version, and the signing verbs. A command that emits no JSON rejects the flag rather than sending human text into your pipeline.

The following table shows the installer's default paths. Configuration keys and the install flags below can change them:

What Default path
Config /etc/emisar/config.yaml
Packs /etc/emisar/packs
Data dir (dispatch log, signing nonces, runtime status) /var/lib/emisar
Cached runner token /var/lib/emisar/token
Events journal /var/log/emisar/events.jsonl

The connect daemon#

emisar connect is the long-running daemon. It dials out over a TLS websocket, advertises this host's actions, executes incoming dispatches, streams redacted output back, and journals every attempt locally. There is no inbound listener on the host. On the first connect it trades the enrollment key (from the env var named by cloud.enrollment_key_env, by default EMISAR_ENROLLMENT_KEY) for a cached per-runner token. The key can be unset afterward.

You normally do not run connect by hand. The installer registers it as a supervised service (emisar.service under systemd, a LaunchDaemon on macOS) running as the emisar user. Manage it through the init system; reload sends SIGHUP, which re-reads packs, rebuilds the signing verifier, and re-advertises the catalog on the live connection — in-flight runs keep executing across it.

shell
$ sudo emisar status               # daemon, connection, catalog, and in-flight runs
$ sudo systemctl status emisar     # is the service process up?
$ sudo journalctl -u emisar -f      # stream its logs
$ sudo systemctl reload emisar      # re-read packs + signing config (SIGHUP)
$ sudo systemctl restart emisar     # full restart

On a --no-service host, such as a container, CI box, or cloud shell, there is no unit. Run the daemon with emisar connect --config /etc/emisar/config.yaml. The runner fleet guide covers groups, enrollment keys, and pack credentials.

Actions#

Inspect the catalog that this host advertises. Run one action locally while debugging a pack. emisar actions is an accepted alias for the group.

Local actions bypass the cloud gate
Use emisar action run only to debug a pack on a host you control. It bypasses cloud policy, approvals, signed dispatch, and cloud audit. It still writes the runner's local event.
Command What it does Mode
emisar action list Every action this host advertises — id, pack, kind, risk, title. Reads only
emisar action describe <id> The full JSON definition of one action, including its argument schema. Reads only
emisar action run <id> Execute one action on this host now, for debugging a pack. Runs on the host

Flags on action run:

  • --arg key=value — one argument, repeatable. Numbers, true/false, and JSON arrays/objects are decoded. Everything else stays a string.
  • --reason "…" — free-text reason recorded on the local event.
  • --timeout 30s — override the action's timeout, clamped to its own min/max.
  • --stream — stream stdout/stderr live instead of returning buffered JSON.

Packs#

emisar pack, also available as packs, manages this host's action catalog. The catalog contains the versioned action schemas that the host can execute.

Command What it does Mode
emisar pack suggest Recommend packs for what is running on this host, from the public registry or a --catalog URL, file, or pack directory. Reads only
emisar pack list Installed packs with version, action count, and content hash. Reads only
emisar pack info <id> A pack's setup. Required binaries, the env vars its actions read, a verify command. Reads only
emisar pack verify [id...] Run each pack's declared verify action to check it can reach and authenticate to its target. Reads its target
emisar pack validate <path> Validate a pack directory on disk without loading it into the runner. Reads only
emisar pack diff <id> The changed lines between an installed pack and the registry's, with risk and redaction changes called out. Reads only
emisar pack install <name|path|url> Fetch, validate, hash-verify, and install one pack into the packs dir. Changes the host
emisar pack update [id...] Update installed packs to the registry's current versions. Changes the host
emisar pack uninstall <id> Remove an installed pack. Aliases are rm, remove, and delete. Changes the host

Flags on pack install:

  • --hash sha256:… — pin the exact content hash the portal advertises. The install aborts on any mismatch. A tampered or mismatched copy never reaches the runner.
  • --force — overwrite a pack already installed under the same id.
  • --registry <url> — install from your own registry (or set EMISAR_PACKS_REGISTRY). Defaults to the public registry at emisar.dev.
  • --dest <dir> — the packs directory to install into. Defaults to the config's first paths.packs entry.

Flags on pack diff (which also takes --registry):

  • --to <version> — compare against a specific published version instead of the registry's current one. That URL carries no index entry, so the computed hash is printed for you to pin the install with.
  • --stat — the summary and the callouts without the changed lines.
shell
# install one pack, pinned to the hash the portal advertises
$ sudo emisar pack install redis --hash sha256:9f2c…
# see what an upgrade would change, line by line, before applying it
$ sudo emisar pack diff redis
# update every installed pack (or name a few); preview with --dry-run
$ sudo emisar pack update
$ sudo emisar pack update redis postgres --dry-run
# validate a pack you are authoring, before you trust it
$ emisar pack validate ./my-pack

install, update, and uninstall signal a running daemon to reload — the same SIGHUP as systemctl reload emisar — so the runner advertises changed actions without restarting or dropping a run; when no live daemon is reachable, the CLI prints the manual reload command instead. Installing a pack does not trust it: a changed hash lands as pending in Packs until someone trusts it. Names accept name=version to pin a specific published version. See the pack reference for what goes in a pack.

A pack that installs cleanly can still be missing a credential. pack verify runs the low-risk read each pack declares for the purpose, so a wrong environment variable surfaces on the host instead of at the first dispatch. pack install runs it for the pack it just installed.

shell
# probe every installed pack; name packs to probe only those
$ sudo emisar pack verify

ok       docker    docker.ps        26ms
ok       redis     redis.info       18ms
skipped  gcp-dns   gcp.dns_zones    needs project — probe it with: emisar pack verify gcp-dns --arg project=<value>
failed   postgres  postgres.uptime  failed (exit 2): psql: error: connection to server at "db.internal", port 5432 failed: FATAL:  password authentication failed for user "emisar"

2 ok · 1 failed · 1 skipped

# supply what the host cannot infer
$ sudo emisar pack verify gcp-dns --arg project=acme-prod

A skip is not a failure. A pack whose verify action needs a value this host cannot infer — a project id, a hostname, a pid — is skipped with the command that completes it, as is a pack your admission config refuses. Only a real failure exits non-zero. Read the detail before concluding the pack is misconfigured: a target that is simply down fails the same way a bad credential does.

Diagnose and audit#

When a runner will not connect or an action fails, these read-only verbs answer why without opening a cloud session. What each answer means, and which page owns the fix, is in troubleshooting.

Command What it does Mode
emisar status Correlate the live daemon lock and PID with its owner-only runtime snapshot. Reports the last successful heartbeat send, advertised catalog, uptime, connection attempts, and in-flight runs. Exits nonzero when the daemon or connection is unhealthy. Reads only
emisar doctor Offline preflight: config, credential, dispatch log, pack dirs, packs, action binaries, service, and control-plane reachability. Exits nonzero if any check fails. --probe adds the pack verify run. Reads only
emisar state Print the runner_state JSON this host advertises to the control plane. Reads only
emisar events tail Print recent journal events. -f follows new events. --lines sets the count and defaults to 50. Reads only
emisar events cat / grep Dump the whole journal, or filter it by --action, --type, --event-id, or --request-id. --type is the event_type a SIEM alerts on. Reads only
emisar audit verify [path] Re-derive a journal file's SHA-256 hash chain and report breaks. --all checks rotated siblings. Reads only
emisar version The runner version and build info. Reads only
shell
$ sudo emisar status              # running daemon + connection summary
$ sudo emisar doctor              # offline preflight when status reports a problem
$ sudo emisar events tail -f      # the local journal, live
$ sudo emisar audit verify --all  # re-derive the journal hash chain

status reads a mode-0600 snapshot written by the running daemon and verifies it against the held process lock, PID, and heartbeat freshness. It proves only the host's latest successful send; the console is authoritative for current control-plane receipt. doctor never opens a cloud session, and executes no action unless you pass --probe, which adds the pack verify run above and so costs one real call per pack. One failing check never aborts the rest, so one run surfaces every problem. audit verify exits 0 when the chain is intact and 1 on the first break, printing the line and event ID; --all walks the active file plus every rotated .N sibling, verifying each chain independently.

Tamper-evident, not tamper-proof.
audit verify verifies continuity only within the retained journal or retained suffix. It cannot prove that a privileged host operator did not replace or truncate the entire local journal. The record that cannot be changed from the host is the off-host cloud audit. Carry both into your SIEM and cross-reference them. See Audit & SIEM.

Signed-dispatch keys#

emisar signing sets up bridge-attested dispatch. An enforcing runner then requires a CA-signed certificate on every action. A compromised control plane can relay a dispatch but never mint one. Everything here is offline: keys are printed to your terminal and are not sent anywhere.

Command What it does Mode
emisar signing init Mint a CA, leaf key, and certificate. Print the runner config, offline CA private key, and two MCP environment variables. Prints key material
emisar signing new-ca Mint just the offline CA keypair — the root of trust (rarely). Prints key material
emisar signing new-cert Sign a short-lived operator cert against the offline CA (routinely, as certs expire). Prints key material
  • --ca-name (init and new-ca) — the CA's common name, defaulting to emisar-dispatch-ca.
  • --ttl 24h and --scope group=prod,env=edge (init and new-cert) — the cert's validity window, which accepts Go durations plus long-form 30d/ 1y and defaults to 24h, and the runners it binds to. Short is better — the only revocation is expiry, so a long TTL keeps a leaked cert usable longer. An empty scope means any runner that trusts the CA.
  • --ca-key and --ca-cert (both required on new-cert) — the offline CA private key and certificate from new-ca. Both are read locally and never transmitted; the certificate is required because an X.509 leaf names its issuer exactly.
  • --key-name (new-cert) and --key ed25519|p256 (all three) — the leaf's common name, and the key algorithm. Choose p256 when a KMS or HSM in your chain cannot do Ed25519.
shell
# one-shot: prints the runner config block, the offline CA key, and the MCP env vars
$ emisar signing init --ca-name acme-2026 --scope group=prod --ttl 24h
# later, renew a cert against the offline CA (no runner reload needed)
$ emisar signing new-cert --ca-key <offline CA key> --ca-cert <CA certificate> --scope group=prod

The generated EMISAR_SIGNING_KEY and EMISAR_SIGNING_CERT are environment variables for the MCP client. The CA private key stays offline. Never put it on a runner or the control plane. Signed dispatch explains enforcement, scope, rotation, and revocation.

Install, update, and removal#

Install the binary with install.sh, then use emisar update for manual host-local updates. Both paths use the same staged replacement transaction and preserve config and packs. The installer requires systemd on Linux (use --no-service for hosts without a real init). This section is the flag reference. Upgrade runners explains fleet canaries, verification, batches, and rollback.

emisar update requires the root-owned receipt written by the official installer. It refuses copied, containerized, development, and infrastructure-managed binaries. Update those from the system that deployed them.

shell
# update an official installer-managed runner
$ sudo emisar update
$ sudo emisar update --version X.Y.Z
# install or provision a host
$ curl -fsSL https://emisar.dev/install.sh | sudo bash
# provision unattended with a fixed pack set
$ curl -fsSL https://emisar.dev/install.sh | sudo bash -s -- --version runner-vX.Y.Z
$ curl -fsSL https://emisar.dev/install.sh | sudo bash -s -- --yes --packs linux-core,postgres
  • emisar update --version X.Y.Z — install an exact stable immutable runner release for a canary or rollback. Without the flag, the command selects the newest stable immutable release.
  • --version TAG — install a specific runner release (runner-vX.Y.Z, vX.Y.Z, or bare X.Y.Z). Default is the latest.
  • --yes — skip prompts. Requires an explicit --packs (or EMISAR_PACKS) so an unattended run never guesses.
  • --packs LIST — install exactly these packs, comma- or space-separated, with no host detection (for cloud-init, Packer, Ansible).
  • --no-start — install and enable the service but leave it stopped. --no-service — binary only, with no service user, unit, or activation.
  • --uninstall stops the service and removes the binary, unit, and cached token. It keeps config, data, and logs. --purge with it also deletes config, data, logs, and the service user.
  • --bin-dir · --etc-dir · --data-dir · --log-dir · --user — override the install locations and the service account.
shell
# remove the binary, service, and cached token — keep config + journal
$ curl -fsSL https://emisar.dev/install.sh | sudo bash -s -- --uninstall
# …or delete everything, including config, data, and logs
$ curl -fsSL https://emisar.dev/install.sh | sudo bash -s -- --uninstall --purge

After uninstalling, delete the runner from Runners — its run history and audit events survive the host. Removing a runner cleanly, and what changing its enrollment key does, is covered in the runner fleet guide.

Last reviewed September 3, 2026