Skip to content

meroctl CLI Reference

meroctl is the command line for Calimero nodes, over the node API through calimero-client: before anything is sent it fetches the node’s attestation and judges the quote, then every request travels in the sealed channel. The source is crates/meroctl/src.

Its command names differ from the rest of these docs for a few objects (glossary):

Command What it manages
app signed packages (applications), installed once per cluster
context app instances
namespace organizations
group an organization’s teams
account the signed-in account; each passkey is one of its devices

Output is JSON, pretty-printed. Commands that stream (context watch, context presence stream, ops metrics, blob download to stdout, app watch, dev) print their own lines.

Flag Environment Default Meaning
--home PATH CALIMERO_HOME ~/.calimero Directory for meroctl’s saved nodes and sessions ({home}/meroctl/).
--api URL CALIMERO_API none The node’s URL (any node of the cluster).
--node ALIAS the default saved node A node saved with node add, or a node’s URL (http:// or https://, as --api). Conflicts with --api.
--allow-mock off Trust mock quotes (development clusters such as merod local). A flag only, never read from the environment; node add --allow-mock remembers it for one saved node.
--allow-debug-profiles off Trust a release’s debug profiles (the host can read a debug image’s memory).
--tee-profiles NAME,... CALIMERO_TEE_PROFILES none Image profiles a real node’s signed mero-tee release must list it under. Without any, every real quote is refused.
--min-release VERSION CALIMERO_MIN_RELEASE 2.3.109 The oldest mero-tee release to trust.
--api-key KEY CALIMERO_API_KEY none Authenticate with this API key instead of a saved session.
--ops-token TOKEN CALIMERO_OPS_TOKEN none The operator’s token, for ops commands.
--operator-key-file FILE CALIMERO_OPERATOR_KEY_FILE none The operator’s Ed25519 key (32-byte seed, hex): ops release, ops outbound, ops upgrade, ops upgrade-namespace, ops add-node, ops remove-node, ops move, ops set-quotas and ops set-pools sign the decision with it when the node asks (428 OPERATOR_SIGNATURE_REQUIRED).

Which node: --api, else --node, else the default saved node. Which credential: --api-key, else the session saved for that node’s URL (refreshed when it is within 30 seconds of expiring), else none, and the node says what the request needs.

A CONTEXT argument is an alias of the signed-in account or an app id (a context id works too). With no credential, it is used as given.

What an owner approves takes --dev-passkey FILE: a passkey kept in a file, which only a development cluster’s localhost relying party accepts. Without it, the command sends the request plainly and the node answers 412 CONFIRM_REQUIRED: on a real cluster the owner approves in a browser. That holds for context update, context transfer, namespace delete, namespace outbound, namespace approve-release, namespace invite, namespace leave, namespace members add, namespace members remove, namespace capabilities, account outbound, account approve-release, account api-key create, account device revoke, group members add, group members remove, group settings, group leave, group upgrade trigger, group upgrade retry, app origins add and app origins remove. account device add takes --approve-with FILE, a passkey file the account already has.

Saved nodes, in {home}/meroctl/nodes.json (mode 0600). No network.

Command Meaning
node add NAME URL [--allow-mock] Save a node. The first one saved becomes the default. --allow-mock trusts its mock quote. A name is letters, digits, - and _.
node remove NAME (rm) Forget a saved node.
node use NAME Make a saved node the default.
node list (ls) The saved nodes and the default.
Command Route Meaning
login --dev-passkey FILE [--name NAME] [--origin URL] /auth/passkey/register/* or /auth/passkey/login/* Sign in with a file passkey, or sign up when FILE does not exist (--name, default developer, names the account). --origin (default http://localhost:3000) must be one the cluster allows. Needs --allow-mock: on a real cluster use an API key. Saves the session for the node.
logout [--all] /auth/logout-all with --all Forget the saved session; --all first ends every session of the account.
whoami GET /auth/me Who the requests run as.
attest [-c CONTEXT] GET /attestation, GET /apps/{app}/attestation The node’s attestation document; with --context, every replica of that app verified, their receipt keys and the governance they attest.
attest-release [--file FILE --nonce HEX] GET /attestation/release Verify a release attestation (merod attest): fetched from the node’s URL over a fresh nonce (a booting TD’s merod attest --serve), or read from FILE with the nonce it was made over. Judged with --allow-mock, --tee-profiles and --min-release like any quote; needs no session. Prints the claim, the verified registers and the profile they matched.

Signed packages. An application is installed once on the cluster and verified on every node that runs it. meroctl package is the same command (meroctl package origins list PACKAGE).

Command Route Meaning
app install PACKAGE[@VERSION] [--version V] POST /packages/install Install a release from the node’s registry. The version is required, as @V or --version.
app install --path FILE POST /packages or POST /bundles Upload a signed .mpk, or any other file as a raw WASM bundle (its hash printed). Development clusters (--dev) only; production installs PACKAGE@VERSION from the registry.
app list (ls, list-packages) GET /packages Installed applications and their versions.
app get PACKAGE GET /packages/{package} One installed application.
app versions PACKAGE (list-versions) GET /packages/{package} Its installed versions.
app get-latest-version PACKAGE GET /packages/{package} Its newest installed version.
app uninstall PACKAGE [--version V] DELETE /packages/{package}[/{version}] Uninstall one version or the whole application. Refused while a context runs it.
app watch --path FILE --dev-passkey FILE [-c CONTEXT]... [--once] [--interval-ms MS] uploads, then POST /apps/{app}/upgrade Push a build now, then on every save. A .mpk is installed and this account’s contexts of its package (or the --contexts named) are updated to its version; a raw .wasm updates only the --contexts named. --interval-ms defaults to 500.
app origins list PACKAGE (ls) GET /package-origins/{package} The web origins registered for the application: each origin, its rpId, addedBy, addedAt and served (whether the cluster serves the frontend there). Its manager’s only.
app origins add PACKAGE ORIGIN [--rp-id ID] [--served] [--dev-passkey FILE] POST /package-origins/{package} Register a web origin whose pages may sign people in to the application (registered origins). ORIGIN is https://host[:port] as a browser writes it (http only on a localhost name, on a --dev node). --rp-id is the passkey relying party there: the origin’s host (the default) or a parent domain of it, never the cluster’s rp-id or a parent of it; passkeys made there belong to that domain. A package registers at most 8. --served makes it a custom domain: the cluster also serves the application’s frontend there from its signed bundle (https://host, no port; not under the apps domain or the cluster’s rp-id; at most 4 per package and 64 per cluster). The node refuses it 400 NOT_SERVING without --apps-domain and 409 CLUSTER_NOT_UPGRADED while a node runs an older release; an origin held with the other served is 409 ORIGIN_TAKEN (remove it first). Browsers reach it once Calimero Cloud holds its certificate and route (docs/specs/custom-domains.md).
app origins remove PACKAGE ORIGIN [--dev-passkey FILE] (rm) DELETE /package-origins/{package} Remove a registered origin; the sessions opened there end.

The application’s manager handles its origins: where its signing key is bound to a publisher organization, that organization’s org-wide MANAGE_APPLICATION holders, else the account that installed it first. Each change carries the manager’s passkey consent (package:{package} / __add_origin / { origin, rpId }, { origin, rpId, served: true } for a served one, __remove_origin / { origin }): --dev-passkey on a development cluster; without it the node answers 412 CONFIRM_REQUIRED and nothing changes, and on a real cluster the manager makes the change in the console (Web addresses), where the browser holds their passkey. A frontend the cluster serves from the bundle needs no origin here.

App instances.

Command Route Meaning
context create --name NAME (--package P [--version V] | --bundle HASH | --path FILE) [options] POST /apps Create a context. --path installs a .mpk (or a development .wasm) first. See the options below.
context list [--package P] (ls) GET /apps[?package=P] Contexts this account reaches.
context get CONTEXT GET /apps/{app} One context.
context delete CONTEXT (del) DELETE /apps/{app} Delete a context (its owner or an org admin).
context move CONTEXT NAMESPACE [--bring-members] [--dev-passkey F] POST /apps/{app}/move Move the context to another org, its data where it is (whoever manages it now, as an admin of the target org, with a passkey); --bring-members adds the members who reach it.
context transfer CONTEXT ACCOUNT [--dev-passkey F] POST /apps/{app}/transfer Hand the context to another account (its owner, or for an org’s context its owner while a member or an org admin, with a passkey; an org’s context only to a member of the org).
context update CONTEXT [--version V] [--package P] [--service S] [--bundle HASH] [--migrate EXPORT] [--dev-passkey FILE] POST /apps/{app}/upgrade Move to another release or bundle, migrating state where the ABIs say how. --bundle conflicts with --version and --package. The owner approves.
context watch CONTEXT [--from N] [--limit N] (ws) GET /apps/{app}/events Print committed events from index --from (default 0), then live ones, as JSON lines; stop after --limit.
context alias add ALIAS CONTEXT (new, create) PUT /auth/aliases/{alias} Name a context for this account.
context alias remove ALIAS (rm, del, delete) DELETE /auth/aliases/{alias} Remove an alias.
context alias get [ALIAS] GET /auth/aliases/{alias} Resolve an alias (default default).
context alias list (ls) GET /auth/aliases This account’s aliases.
context use CONTEXT PUT /auth/aliases/default Make a context the default (the alias default).
context access CONTEXT owner|org|team|accounts|public [--group TEAM] [--account ID]... POST /apps/{app}/access Who reaches the context. team needs --group; accounts takes --account once per account.
context metadata CONTEXT JSON POST /apps/{app}/metadata Set the context’s metadata; null clears it.
context storage CONTEXT GET /apps/{app}/storage Keys, bytes and blobs the context holds.
context abi CONTEXT GET /apps/{app}/abi The ABI of the bundle it runs.
context presence list CONTEXT (ls) GET /apps/{app}/presence Who is present now (ephemeral, never stored).
context presence publish CONTEXT --seq N [--state HEX] POST /apps/{app}/presence Publish this device’s presence; no --state retracts it.
context presence stream CONTEXT [--limit N] GET /apps/{app}/presence/stream The snapshot, then each change, as JSON lines.

context create options:

Flag Meaning
--name NAME Required. The context’s name.
--package P An installed application. Conflicts with --bundle and --path.
--version V With --package: the release; default the newest installed.
--service S The service, for a multi-service application.
--bundle HASH A raw bundle’s hash (development clusters). Conflicts with --path.
--path FILE A .mpk, or a development .wasm, installed first.
--params JSON Arguments for the app’s init.
--namespace ORG Create it in this organization.
--group TEAM With --namespace: reached by this team’s members (access: team).
--alias ALIAS Also name it with this alias.
Terminal window
meroctl call -c CONTEXT METHOD [--args JSON] [--view [--min-index N]]
[--idempotency-key KEY] [--dev-passkey FILE]
Flag Default Meaning
-c, --context CONTEXT required The context.
METHOD required The method.
--args JSON {} JSON arguments.
--view off Read only (POST /apps/{app}/views): no write, answered by the leader.
--min-index N none With --view: any replica that has applied index N may answer (a stale read).
--idempotency-key KEY none Retries with the same key are answered once.
--dev-passkey FILE none For a method that needs a passkey confirmation per call (development clusters).

A call without --view is POST /apps/{app}/intents. Both answers carry a receipt signed by the answering node.

A context’s blobs.

Command Route Meaning
blob list -c CONTEXT (ls) GET /apps/{app}/blobs The context’s blobs.
blob upload FILE -c CONTEXT POST /apps/{app}/blobs Upload a file; prints its id.
blob download ID -c CONTEXT [-o FILE] GET /apps/{app}/blobs/{id} Write a blob to --output, or to standard output.
blob info ID -c CONTEXT GET /apps/{app}/blobs/{id}/info A blob’s size.
blob delete ID -c CONTEXT (rm) DELETE /apps/{app}/blobs/{id} Forget a blob (its owner or an org admin); its bytes go once no other context holds them.
blob revoke ID -c CONTEXT POST /apps/{app}/blobs/{id}/revoke Invalidate the download grants made so far for a published blob (its owner or an org admin).

A context’s sealed files.

Command Route Meaning
file upload FILE -c CONTEXT POST /apps/{app}/files, or a session under /apps/{app}/files/uploads Upload a sealed file (its owner or an org member): one request up to 16 MiB, else parts that resume after a dropped connection; prints its id, size, sha256 and index.

Organizations. Alias: ns. ROLE is guest, member, admin or owner; JOIN is open (whoever reaches the parent is a member) or restricted (only the members added).

Command Route Meaning
namespace create NAME POST /orgs Create an organization.
namespace get ORG GET /orgs/{org} One organization.
namespace list (ls) GET /auth/me The organizations this account belongs to, with its role.
namespace delete ORG [--dev-passkey FILE] DELETE /orgs/{org} Delete it with its teams, invites and contexts (the owner’s).
namespace invite ORG [--role ROLE] [--group TEAM]... [--ttl-secs N] [--uses N] POST /orgs/{org}/invites Make an invitation token. --role defaults to member; --group adds teams the invitee joins.
namespace join TOKEN POST /invites/{token}/redeem Join with an invitation token.
namespace leave ORG DELETE /orgs/{org}/members/{me} Leave the organization.
namespace groups ORG GET /orgs/{org} The organization’s teams.
namespace create-group ORG NAME [--parent TEAM] [--join JOIN] POST /orgs/{org}/teams Create a team; --join defaults to restricted.
namespace members list ORG [--security] (ls) GET /orgs/{org} Its members. --security (owners and admins, GET /orgs/{org}/members?detail=security): each member’s passkeys held (to 3+), recovery code set, last active day and live sessions.
namespace members add ORG ACCOUNT [--role ROLE] (set-role) POST /orgs/{org}/members Add a member or change their role (default member). owner moves ownership.
namespace members remove ORG ACCOUNT (rm) DELETE /orgs/{org}/members/{account} Remove a member.
namespace metadata get ORG GET /orgs/{org} The organization’s metadata.
namespace metadata set ORG JSON POST /orgs/{org}/metadata Replace it.
namespace member-metadata ORG ACCOUNT JSON POST /orgs/{org}/members/{account}/metadata Set a member’s metadata (theirs, or an admin’s); null clears it.
namespace capabilities ORG BITS [--account ID | --group TEAM] POST /orgs/{org}/capabilities Set the capability bits of a member, of a team’s members, or (neither) the organization’s default.
namespace outbound ORG --package P [--prefix URL]... [--replace] [--dev-passkey FILE] POST /orgs/{org}/outbound Approve URL prefixes the package’s publisher may call out to from the organization’s contexts (default: every prefix its latest release declares). --replace sets exactly these, which is how one is revoked. Where the owner holds outbound approvals.
namespace approve-release ORG VERSION [--dev-passkey FILE] POST /orgs/{org}/releases Approve a mero-tee release to hold the organization’s contexts. Where the owner holds release approvals.

An organization’s teams. ROLE is lead, member or read-only.

Command Route Meaning
group get ORG TEAM GET /orgs/{org}/teams/{team} One team.
group update ORG TEAM [--name NAME] [--join JOIN] POST /orgs/{org}/teams/{team} Rename it or change who may join.
group delete ORG TEAM [--dev-passkey FILE] DELETE /orgs/{org}/teams/{team} Delete it with the teams below it and their contexts; with contexts there, approved with a passkey (412 names them).
group reparent ORG TEAM (PARENT | --top) POST /orgs/{org}/teams/{team} Move it under another team, or to the top.
group subgroups ORG TEAM GET /orgs/{org} The teams directly below it.
group members list ORG TEAM (ls) GET /orgs/{org}/teams/{team} Its members and read-only members.
group members add ORG TEAM ACCOUNT [--role ROLE] [--capabilities BITS] (set-role) POST /orgs/{org}/teams/{team}/members Add a member or change their role (default member) or capabilities.
group members remove ORG TEAM ACCOUNT (rm) DELETE /orgs/{org}/teams/{team}/members/{account} Remove a member, from the teams below it too.
group metadata ORG TEAM JSON POST /orgs/{org}/teams/{team} Set the team’s metadata.
group settings ORG TEAM --capabilities BITS POST /orgs/{org}/capabilities The capabilities every member of the team gets.
group leave ORG TEAM DELETE /orgs/{org}/teams/{team}/members/{me} Leave the team.
group upgrade trigger ORG --package P --version V [--group TEAM [--cascade]] [--migrate EXPORT] [--dev-passkey FILE] POST /orgs/{org}/upgrade Move the organization’s contexts of P (or one team’s, with --cascade its subtree) to a release, as one run the owner approves once.
group upgrade status ORG RUN GET /orgs/{org}/upgrades/{run} Follow a run.
group upgrade retry ORG RUN [--dev-passkey FILE] POST /orgs/{org}/upgrades/{run}/retry Run its failed contexts again, as a new run.

The signed-in account.

Command Route Meaning
account show GET /auth/me The account, its devices, organizations and approvals.
account usage GET /auth/usage What it uses against its quotas.
account device list (ls) GET /auth/me Its devices (passkeys) and the current one.
account device add --dev-passkey FILE --approve-with EXISTING [--origin URL] /auth/passkeys/add/* Add a device: a new file passkey (development clusters), approved with a passkey file the account already has. FILE must not exist.
account device label CREDENTIAL LABEL POST /auth/passkeys/{cred} Name a device; "" clears the name.
account device revoke CREDENTIAL [--dev-passkey FILE] DELETE /auth/passkeys/{cred} Remove a device; its sessions end. Approved with a passkey.
account session list (ls) GET /auth/sessions Its live sessions: the device each was opened with, the application and origin it is bound to, when it opened and was last active (to within about an hour), and the current one.
account session revoke SESSION DELETE /auth/sessions/{session} End one session.
account activity [--after T] GET /auth/activity What its devices did, newest first: the applications and origins each signed in to, and the changes each approved (needs a passkey session).
account api-key create [--label L] [--context C]... [--read-only] [--ttl-secs N] POST /auth/apikeys Create an API key (needs a passkey session). --label defaults to meroctl; --context limits it to those contexts. The key is printed once.
account api-key list (ls) GET /auth/apikeys Its API keys.
account api-key revoke KEY_ID [--dev-passkey FILE] DELETE /auth/apikeys/{key} Revoke one, approved with a passkey.
account approve-release VERSION [--dev-passkey FILE] POST /auth/releases Approve a mero-tee release to hold the account’s own contexts.
account outbound --package P [--prefix URL]... [--replace] [--dev-passkey FILE] POST /auth/outbound Approve outbound prefixes for the account’s own contexts.
Command Meaning
dev start --path FILE --dev-passkey FILE [--name NAME] [-p JSON] [-w] Install a build (.mpk, or a development .wasm), create a context from it (--name, default dev; --params for init) and, with --watch, push every later save to it.
dev watch --path FILE --dev-passkey FILE [-c CONTEXT]... [--once] [--interval-ms MS] app watch under another name.
Command Route Meaning
network status GET /health This node’s id, its release, and who leads the system group.

The operator’s commands. Every one needs --ops-token (or CALIMERO_OPS_TOKEN). The Ops API has no read path into apps’ data. Decisions the deployment gave the operator are made here, signed with --operator-key-file (Operator); the node answers 403 for the others.

Command Route Meaning
ops cluster GET /ops/cluster The cluster’s name and identity key (hex), which merod run --job-node --cluster-identity takes.
ops status GET /ops/status This node, the system group’s and hosted groups’ Raft status, governance, fleet and counts.
ops metrics GET /ops/metrics Prometheus text.
ops placement GET /ops/placement Each context’s group and replica node ids, each node’s zone, release and context count. Ids and counts only.
ops add-node ID ADDR [--zone Z] [--role replica|job] [--pool P] POST /ops/nodes Register a node so the system group admits it as a learner (and a voter while fewer than 3 voters are left). ADDR is its URL. --role job registers a job node (merod run --job-node): it is never admitted, placed or sent the cluster key. A node’s role is fixed until it is removed.
ops remove-node ID DELETE /ops/nodes/{id} Remove a node no app keeps a replica on; run against the system leader.
ops move CONTEXT --replicas ID,... POST /ops/apps/{app}/replicas Move a context to these replica node ids. A follower answers 421 NOT_LEADER; meroctl retries at the leader it names. The set it already has answers unchanged: true; another move of it running, 409 MOVE_IN_PROGRESS.
ops backup POST /ops/backup Write encrypted snapshots of the contexts this node leads now.
ops quotas GET /ops/quotas The per-account quotas.
ops set-quotas JSON POST /ops/quotas Replace them.
ops pools GET /ops/pools The compute pools and the runs each takes at once.
ops set-pools JSON POST /ops/pools Set the pools, { name: { runsPerNode, maxMemoryMib } }; a job node joins one with add-node --pool.
ops release VERSION [--withdraw] POST /ops/releases Approve (or withdraw) a mero-tee release for every app, where the operator holds release approvals.
ops outbound --package P [--prefix URL]... [--replace] POST /ops/outbound Approve outbound prefixes cluster-wide, where the operator holds outbound approvals.
ops packages GET /ops/packages The applications the operator installed (not the accounts’).
ops install PACKAGE[@VERSION] [--version V] / ops install --path FILE POST /ops/packages/install, POST /ops/packages Install a release for the operator’s upgrades, from the registry or a signed .mpk.
ops uninstall PACKAGE [--version V] DELETE /ops/packages/{package}[/{version}] Uninstall what the operator installed.
ops upgrade CONTEXT [--version V] [--package P] [--service S] [--bundle HASH] [--migrate EXPORT] POST /ops/apps/{app}/upgrade Update a context to an installed release, where the operator holds app upgrades.
ops upgrade-namespace ORG --package P --version V [--group TEAM [--cascade]] [--migrate EXPORT] POST /ops/orgs/{org}/upgrade Update an organization’s contexts (or one team’s) to a release; follow it with group upgrade status.
Terminal window
meroctl [--node ALIAS|URL] proxy [--listen 127.0.0.1:2580] [--allow-remote] [--openapi FILE]

Serves the node’s API as plain HTTP on this machine, for curl, scripts and clients generated from the node API’s OpenAPI document (node API): every request is sent to the node through the same attested, sealed client every other command uses, so it attests the node with the same --allow-mock, --tee-profiles and --min-release, before it takes any request. Method, path, query, headers and body go to the node as they came; the node’s status, headers and body come back as they come, and an event stream (/apps/{app}/events, /apps/{app}/presence/stream) stays a stream. A refusal is the node’s own ({ error, message, details }), a 412 CONFIRM_REQUIRED included: the caller signs the challenge and sends the request again with confirm. On start it prints one JSON line, { "listening": "http://127.0.0.1:2580", "node": URL }, and one line per request on stderr (method, path, status).

Flag Default Meaning
--listen ADDR 127.0.0.1:2580 Where to serve (:0 picks a free port). A non-loopback address is refused without --allow-remote.
--allow-remote off Serve on a non-loopback address and take requests for any Host, with a warning: anyone who reaches the port acts as you.
--openapi FILE none Serve this document at GET /openapi.json. Without it, the node’s own if it serves one, else 404 with a note. Either way servers is set to the proxy.

Which credential a request carries: its own Authorization, passed through as given and never refreshed; else --api-key; else the session login saved for the node, refreshed on a 401 and saved again; else none.

What it refuses, because it holds your credential:

  • a Host that is not localhost or a loopback address (DNS rebinding), unless --allow-remote: 403 HOST_REFUSED;
  • a request a browser sent for a web page (it carries Origin, or Sec-Fetch-Site other than none), since any page you open could otherwise act as you: 403 BROWSER_REFUSED. The proxy sends no CORS headers;
  • a body over 64 MiB, the sealed envelope’s cap: 413 TOO_LARGE;
  • a request when the node cannot be reached, attested or understood: 502 UPSTREAM, and nothing is sent in the clear.

It never logs a header, a query or a body. See Use any HTTP tool.

Terminal window
meroctl raw METHOD PATH [BODY]

Any API route, through the same attested, sealed client. METHOD is get, post, put or delete; BODY is JSON (default {} for post and put). Use it for what no command covers, such as POST /auth/apikeys with methods or allowConfirm. It cannot answer a 412 CONFIRM_REQUIRED: a route that needs a passkey assertion needs a command with --dev-passkey, or a browser. See the node API reference.

Terminal window
merod local # in another terminal
meroctl node add local http://127.0.0.1:2501/ --allow-mock
meroctl login --dev-passkey ./dev-passkey.json --name ada
meroctl app install --path ./kv-store.mpk
meroctl context create --package com.example.kv --name notes --alias notes
meroctl call -c notes set --args '{"key":"a","value":"1"}'
meroctl call -c notes get --args '{"key":"a"}' --view
meroctl --ops-token "$TOKEN" ops status