The TUI
Bare
lm(orlm ui) opens the k9s-style console (the Kubernetes operators' console — same navigation idiom) — the installer's daily screen.:is the command palette (type a page name, Tab/Enter to commit);?opens the in-app help;qquits.
This page tours the TUI by task — what you press to watch an ingestion, decide on a pending item, ask your data a question, or create something new. The key reference at the bottom lists every gesture in one table if you already know what you're looking for.
Getting around
: opens the palette — start typing a page name (:so offers :sources) and press
Tab or Enter to commit. The pages: :new :ask :inbox :processes :view
:modules :sources :conflicts :observations :outputs :endpoints :runs
:dlq :hooks :migrations :scopes (:connectors still works — it's an older
spelling that resolves to :sources).
Global keys, from any page:
| Key | Effect |
|---|---|
: |
open the command palette |
r |
refresh the current page |
d |
toggle the describe panel (full detail on the selected row) |
y |
yank the current row — copies it to your local clipboard, even over SSH |
q |
quit |
? |
help screen (the same table, always at hand) |
Esc |
close a modal, cancel an input, or back out one level |
:runs, :dlq and :inbox live-update — new rows appear without pressing r.
Session expired. If your login expires or is rotated mid-session, the TUI raises
a session-expired modal instead of failing silently. Press L to log in again —
the same OIDC device flow as lm login: a URL plus a short code, approved on your
identity provider's sign-in page, and the TUI resumes automatically — or Esc to
dismiss it for now (it reappears on the next request that needs the dead session).
Watch an ingestion
:sources is the home page — where your data comes from, one row per connector instance.
| Column / key | Where | What it tells you |
|---|---|---|
SCHED |
:sources |
the connector's schedule |
ENABLED |
:sources |
whether the connector is on at all |
Enter |
:sources |
REST source → drills into :endpoints; any other kind → jumps straight to :runs, filtered to that connector |
SCHEDULE, LAST_RUN, STATUS |
:endpoints |
per endpoint — for a REST source, this is where to check "did it even fire?" without leaving the table |
Enter |
:endpoints |
drills into that endpoint's own :runs — the title says [endpoint=…], and Esc clears it |
at: (under ── last run ──) |
d describe panel, any source |
the exact last-run time, even when it isn't a table column |
In :sources: s cycles the sort column, e opens the connector's YAML in
$EDITOR for a live edit, R (or ^R) triggers a run now, ^P pauses or resumes
it, Del deletes it (no confirmation), t edits its tags.
Reading a run. :runs lists CONNECTOR, ENDPOINT, STATUS, STARTED, DURATION,
RECORDS (in/out/skip) and TRIGGER per row — one row per endpoint, which is why the endpoint
is named: a connector reading two tables writes two rows that differ in nothing else. Select one and press d for the full
record — the same fields plus the run's id, chunk count, and, when the run failed,
a final error: section with the failure message in full.
Stopping a run. Select a Running row and press c to request cancellation.
That is a request, not a stop: the chunk in flight keeps going, and the run ends
only at the next chunk boundary — it can still finish normally if that chunk was
already reaching the end of its data. A toast confirms once the hub has recorded
the request. Pressing c on a row that already left Running, or on a run no
chunk loop on this hub is watching (an orphan the boot-time reaper hasn't caught
yet — it clears such rows to Failed at the hub's next restart — or one just
triggered a moment ago and not yet registered — retry then), toasts the hub's own
refusal instead of doing nothing silently.
STATUS takes exactly five values. The table below decodes every one of them, in the
order to check when a run's outcome isn't obvious:
| What you see | What happened | Where the detail lives |
|---|---|---|
STATUS = Running |
The run hasn't reached a terminal status yet — it's still going | :runs live-updates on its own; press d to watch progress so far |
STATUS = Failed |
A connection or manifest problem (source unreachable, bad credentials, a refused config) failed the run itself | The error: section in d's full record |
STATUS = Completed, a row is in :dlq |
A quality failure on that one row (e.g. validate-row) |
:dlq — reason and payload |
STATUS = Completed, row counted in skip but not in :dlq |
The row was excluded by a declared filter (filter-where, skip-empty-rows), not rejected |
The skip figure in RECORDS |
STATUS = Partial |
The run ended below the target it gave itself — it read less than the source held when it started. Nothing broke and nothing was written twice: the position is kept and the next run resumes from it. Two causes: the reader stopped on its own budget (a topic feeding only unparsable messages), or the run began past records the source had already deleted underneath its stored position | The error: section in d's full record names the target and what was not reached |
STATUS = Cancelled |
Either cancelled by a user mid-run, or this run was skipped because another run for the same endpoint was already active | The error: section in d's full record — cancelled by user or skipped: another run is already active for this endpoint |
No entry in :runs at all |
Nothing failed — the schedule hasn't fired yet | :sources / :endpoints, not :runs |
Every dead-letter is also counted in skip — :dlq is always a subset of it,
never more — so in/out/skip alone already tells you whether a run dropped
anything, DLQ'd or not.
Once you have the cause: fix it (e to edit the connector's YAML, or the secret/env
value on the hub if that's the problem), then R to run it again. The full walk —
including the dead-letter reasons table — is Diagnosing a failed
run.
Once you're watching runs, :dlq is next: the dead-letter queue, where a row
that a connector could not place waits with its reason and payload rather than being
silently dropped. ^R marks a dead-letter replayed, ^D discards it.
Decide
Four pages answer "what needs me":
:inbox— the tenant'sdecision.*events, business decisions raised by processes and workflows, never run failures (run health lives in:runs, above).aacks the selected one (idempotent — acking twice is harmless);Atoggles whether already-disposed entries show.:outputs— what left the substrate and whether it arrived, failures listed first.^Rreplays a failed delivery — a succeeded one has nothing to replay, and the TUI says so instead of no-op'ing.:observations— the workflow tape: documents that have NOT moved, stillest first.:conflicts— the fused rows where sources still disagree (read-only — resolving one is an upstream manifest change, never a keystroke here).
Explore & ask
:ask is analytic chat — ask your data a question in plain language and it comes
back as generated SQL plus rows plus a plain-language answer. Enter submits, y
yanks the generated SQL, e exports the result rows to a CSV file, n clears the
input for a new question. The scope it asks against is the session scope shown in the
header: seeded at startup from your default (lm scope), changed with :scopes →
Enter or :scope <tag> for the rest of the session.
:view opens a kind:View manifest file
and renders it — Enter opens the suggested living example on an empty path, or type
your own.
| Key | Effect |
|---|---|
Enter (on a rendered row) |
drills into detail, with the _conflicts ledger |
f |
adds a server-side filter (a bad one is refused with the reason, not silently dropped) |
F |
clears all filters |
g |
generates a view from a plain-language sentence, over the active scope — set one first with :scopes or the :scope <tag> palette shortcut |
a |
applies the rendered view into the hub's saved-view store |
o |
opens another file |
r |
refreshes |
Create
:new is the authoring loop's front door: pick a kind, Enter opens its living
example in $EDITOR. On save, the manifest validates (or applies directly, for
kinds whose apply step is the validation) — a clean pass applies it and jumps to
where you'd observe it; issues show field-by-field and drop you into the issues
view, where e re-opens the same buffer for another edit and Esc abandons it —
but never destroys it: your edits are written to a temp file first, so nothing is
ever lost to a stray Esc.
:processes lists declared
kind:Process rules; e re-opens a
stored manifest through that same edit → validate → apply loop, so create and edit
share one path.
Admin corner
:modules lists installed business modules. :hooks lists installed Kotlin hooks
(read-only — they load from deployment manifests at startup, not applied or
reloaded individually). :migrations shows Flyway status per schema.
:scopes lists the tenant's per-métier scopes (declared as metadata.scopes on connectors and entities) — Enter sets the
active one, as noted above.
Key reference
| Key | Where | Effect |
|---|---|---|
: r d y q ? Esc |
global | palette · refresh · describe · yank row · quit · help · close |
s |
:sources :runs :dlq |
cycle the sort column |
c |
:runs |
request cancellation of the selected run (only meaningful while STATUS = Running) |
e / R (or ^R) / ^P / Del / t |
:sources |
edit YAML in $EDITOR · run now · pause/resume · delete · edit tags |
Enter |
:sources |
REST → its :endpoints; others → its :runs |
Enter / Esc |
:endpoints |
the endpoint's :runs · back to :sources |
^R / ^D |
:dlq |
mark replayed · discard |
^R |
:outputs |
replay the selected failed delivery (a new attempt is recorded) |
a / A |
:inbox |
ack the selected decision (idempotent) · toggle disposed ([traité]) |
Enter / e / Esc |
:new |
edit the kind's living example in $EDITOR · re-edit after refusals · abandon (buffer kept in a temp file) |
e |
:processes |
edit the stored manifest through the same validate → apply loop |
Enter / f F / g / a / o / r |
:view |
record detail (_conflicts ledger) · add/clear server-side filters (refusals name the reason) · generate from a sentence (AI) · apply the rendered view to the hub store · open another file · refresh |
Enter |
:scopes |
set the active scope (used by :ask) |
Enter / y / e / n |
:ask |
run · yank the SQL (OSC52) · export rows to CSV · new question |
See also
- CLI reference — the same surface, driven from scripts and CI instead of the console.
- Diagnosing a failed run — the full walk from a
failed run to a fix,
lm run get/lm dlq getand the dead-letter reasons table. - Connectors overview — what a source, an endpoint, and a run actually are.
- Inbox — the decisions the TUI's
:inboxpage surfaces.