LEMC verbs
A recipe writes ordinary lines to stdout. Lines that begin with a
recognized LEMC verb become structured output events before the server
fans them out to the browser and lemcli.
Recipes stay language-agnostic: if a program can print a line, it can emit a LEMC verb.
Wire format
verb.name;payload
The first semicolon separates the verb from its payload. The server recognizes supported verbs, records the corresponding event, and keeps unrelated stdout/stderr as durable diagnostic output.
Verb reference
| Verb | Effect | Typical payload |
|---|---|---|
lemc.html.append | Append HTML to the task's rendered output target. | <p>ready</p> |
lemc.html.trunc | Replace the current rendered HTML before applying the payload. | <h2>new view</h2> |
lemc.css.append | Append CSS to the task-scoped style target. | .ready { color: green; } |
lemc.css.trunc | Replace current task CSS with the payload. | #target { display: grid; } |
lemc.js.exec | Execute the supplied task JavaScript; alias for truncate-and-execute behavior. | console.log("ready") |
lemc.js.trunc | Replace current task JavaScript and execute it. | document.querySelector(...) |
lemc.env | Carry a key/value into later recipe steps. | REPORT_ID=42 |
lemc.output | Append escaped plain text to the visible output stream. | readiness check passed |
lemc.err | Append a user-visible error and fail the job. | release gate rejected |
Minimal shell example
#!/bin/sh
set -eu
printf '%s\n' 'lemc.html.trunc;<section class="report"><h2>Ready</h2></section>'
printf '%s\n' 'lemc.css.append;.report { padding: 1rem; color: #315b35; }'
printf '%s\n' 'lemc.output;release readiness checks completed'
printf '%s\n' 'lemc.env;READINESS_STATUS=ready'
The HTML and CSS appear through the shared event path. The plain
output remains readable in both clients, and
READINESS_STATUS becomes available to the next step.
The recipe environment contract
A LEMC task runs in a fresh runtime, but its authorized state does not have to start over. Before the first container starts, LEMC hydrates the scoped workspace and assembles the declared environment. Every step sees the appropriate mounted files. Before teardown, LEMC synchronizes accepted state, artifacts, logs, render cache, and terminal proof so a later task can continue the work.
The variables below identify the admitted job and its output targets. Read them; recipe code must not invent identity or authority.
| Standard variable | Use it for |
|---|---|
LEMC_STEP_ID | Identify the current step number. |
LEMC_SCOPE | Distinguish individual and shared work. |
LEMC_USER_ID, LEMC_USERNAME | Name the initiating user context. |
LEMC_UUID, LEMC_RECIPE_NAME, LEMC_PAGE_ID | Identify the cookbook or app, recipe, and page. |
LEMC_HTML_ID, LEMC_CSS_ID, LEMC_JS_ID | Target this task's rendered output without colliding with another job. |
LEMC_HTTP_DOWNLOAD_BASE_URL | Build an authorized link to a file written under /lemc/public. |
Create an author-defined value
Define a cookbook form field with variable. LEMC turns
its name into uppercase environment form, replacing spaces and
hyphens with underscores. The submitted value—not its display
label—enters the recipe container.
form:
- variable: release-channel
description: Release channel
type: select
options:
- label: Stable
value: stable
- label: Preview
value: preview
# The selected value is now available to the step.
printf 'lemc.output;channel=%s\n' "$RELEASE_CHANNEL"
# Create a non-secret value for the next step only.
printf '%s\n' 'lemc.env;REPORT_NAME=report.txt'
lemc.env is a private next-step handoff. LEMC does not
cache, log, render, or fan it out to browser and CLI websocket
clients. Put secrets in named server-side bindings and use visible
output or lifecycle verbs only for values callers should observe.
Give each kind of state the right lifetime
The recipe image is read-only and /tmp is ephemeral.
Durable task state belongs under a scoped /lemc
workspace, where LEMC can validate, synchronize, and hydrate it
independently of the guest that produced it.
| Boundary | Lifetime and purpose |
|---|---|
/lemc/private | Private durable workspace for this user and context. Keep working state such as terraform.tfstate here so a later authorized task can hydrate and use it. |
/lemc/public | Durable downloadable artifacts. Link an exact task result with LEMC_HTTP_DOWNLOAD_BASE_URL. |
/lemc/global | Cookbook UUID or locker-scoped data used across the cookbook context. |
/lemc/shared | Team workspace mounted only when the recipe runs in shared scope. |
lemc.env | One small, non-secret value carried only to the next step in the current job. |
Image root and /tmp | Read-only recipe code plus ephemeral scratch. Do not put state here when another step or task will need it. |
Example: preserve Terraform state after the runner is gone
An apply task writes Terraform's state into the private workspace. A second step can turn that state into a downloadable summary. A destroy task hours later enters a new guest, receives the same authorized private workspace, and acts on the state created by apply.
# Apply task, step 1: update durable private state.
terraform -chdir=/lemc/private apply -auto-approve
test -s /lemc/private/terraform.tfstate
printf '%s\n' 'lemc.env;REPORT_NAME=apply-summary.txt'
# Apply task, step 2: REPORT_NAME arrived through lemc.env.
terraform -chdir=/lemc/private show -no-color > "/lemc/public/$REPORT_NAME"
report_url="${LEMC_HTTP_DOWNLOAD_BASE_URL}${REPORT_NAME}"
printf 'lemc.html.append;<a href="%s">Download apply summary</a>\n' "$report_url"
# Later destroy task: LEMC hydrated the private workspace first.
test -s /lemc/private/terraform.tfstate
terraform -chdir=/lemc/private destroy -auto-approve
The compute boundary is disposable; the authorized workspace is not. LEMC verifies durable writes before local scratch deletion, so teardown does not discard the state required for follow-up work.
Scoped targets
LEMC injects task-specific element IDs so recipe CSS and JavaScript can target their own output.
- LEMC_HTML_ID
- The current job's HTML output element ID.
- LEMC_CSS_ID
- The current job's style element ID.
- LEMC_JS_ID
- The current job's script element ID.
selector="#${LEMC_HTML_ID} .result"
printf 'lemc.css.append;%s { font-weight: 700; }\n' "$selector"
HTML, CSS, and JavaScript output is executable content. Publish recipes only through the same review, image authorization, permissions, and isolation policy used for other code.
Link a durable artifact
Write downloadable output under /lemc/public and build
links from the injected immutable task-scoped base URL.
report_path="/lemc/public/readiness.txt"
printf '%s\n' 'release is ready' > "$report_path"
report_url="${LEMC_HTTP_DOWNLOAD_BASE_URL}readiness.txt"
printf 'lemc.html.append;<p><a href="%s">Download readiness report</a></p>\n' \
"$report_url"
The terminal job path syncs the artifact to durable storage before scratch cleanup. Browser and CLI consumers should use LEMC's authorized artifact routes rather than a runner-local path.
Errors and diagnostics
- Use
lemc.errfor a concise failure the caller can act on. - Use ordinary stdout/stderr for bounded technical diagnostics.
- Never print credentials, bearer tokens, registry secrets, or private payloads.
- Do not flood the visible stream with successful pull/provisioning chatter.
- Write durable artifacts when the result is larger than a useful live message.
Multi-step values
# Step 1
printf '%s\n' 'lemc.env;REPORT_NAME=readiness.txt'
# Step 2 receives REPORT_NAME in its environment.
printf 'lemc.output;publishing %s\n' "$REPORT_NAME"
Treat lemc.env as explicit handoff inside one job, not
as durable workspace state or a secret store. Put files a later job
needs under the correct /lemc workspace. Sensitive
inputs belong in server-managed secret/configuration surfaces and
must remain redacted from events and logs.